arrayfire / arrayfire-dotnet

.NET wrapper for ArrayFire
BSD 3-Clause "New" or "Revised" License
78 stars 21 forks source link

Idea about the missing API af_get_raw_ptr #25

Open dotChris90 opened 5 years ago

dotChris90 commented 5 years ago

Hello Arrayfire team!,

I was curious about the implemented APIs for dotnet so far and noticed that there is one is missing (or commented out) : "af_get_raw_ptr".

When using arrayfire you just always get the "unmanaged" C++ af array object as an IntrPtr without any possibility to use this object in other .NET APIs (except arrayfire). In my opinion it would be awesome if .NET would have the underlying array as dotnet object but without duplicating it in memory (this means the raw pointer as .NET array or Span or sth in this way).
If arrayfire-dotnet could do this - you could interact with many other librarties but do not duplicate the memory (as long as on CPU backend I guess).

For this the API af_get_raw_ptr is important. I did some ... try outs and played around with it. I was able to create an arrayfire array, got the raw pointer and created a span object from it. Just want to share with you and ask what you think about. unfortunately until now I did not test if there is really not a duplicate in memory and it was for a very small array so far... plus a lot of unsafe sections

public static class af
    {
        [DllImport("afcpu.dll", ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)]
        public static extern int af_create_array(out IntPtr array_arr, [In] float[] data, uint ndims, [In] long[] dim_dims, int type);
        [DllImport("afcpu.dll")]
        public unsafe static extern int af_get_raw_ptr(void** ptr, IntPtr arr);
    }
    class Program
    {
        static unsafe void Main(string[] args)
        {
            float[] dotnetArray = {101,2,3,4,5};

            float puffer_1 = 5;

            IntPtr af_Ptr = new IntPtr();

            void* puffer_2=(void*) &puffer_1;

            void** raw_ptr = & puffer_2;

            int error = af.af_create_array(out af_Ptr, dotnetArray, 1,new long[] {5},0 );

            error = af.af_get_raw_ptr(raw_ptr,af_Ptr);

            puffer_2 = *raw_ptr;

            var dotnetSpan = new Span<float>((float*)puffer_2, 5); 

        }
    }
9prady9 commented 5 years ago

I am not well versed with dotnet land, perhaps @royalstream @Oceania2018 can provide more in depth feedback or inputs on your suggestion.

Oceania2018 commented 5 years ago

@9prady9 @dotChris90 We should expose the raw data buffer pointer to developers. Actually, arrayfire for python does this also. http://arrayfire.org/arrayfire-python/_modules/arrayfire/array.html

 def raw_ptr(self):
        """
        Return the device pointer held by the array.

        Returns
        --------
        ptr : int
              Contains location of the device pointer

        Note
        ----
        - This can be used to integrate with custom C code and / or PyCUDA or PyOpenCL.
        - No mem copy is peformed, this function returns the raw device pointer.
        - This pointer may be shared with other arrays. Use this function with caution.
        - In particular the JIT compiler will not be aware of the shared arrays.
        - This results in JITed operations not being immediately visible through the other array.
        """
        ptr = c_void_ptr_t(0)
        backend.get().af_get_raw_ptr(c_pointer(ptr), self.arr)
        return ptr.value
dotChris90 commented 5 years ago

@Oceania2018 agree. To be honestly I was investigating a little bit deeper into this stuff.

The best would be API

 public unsafe static extern int af_get_raw_ptr(ref IntPtr ptr, IntPtr arr)

By the way - what also would be interesting is the following. It is possible to create a Memory< T > object by this raw pointer. Memory< T > can be set as a property to classes which is a extrem benefit over Span < T >. With this it would be possible to use the pointer in other .NET libs without copy the data always from C++ to .NET. The alternative would be using get_data API - but this copies the data into managed memory every time you use this api. With Memory < T > you can store this array on heap.

public class RawPrtManager<T> : MemoryManager<T>
{
    protected IntPtr _rawPtr;
    protected int _length; 
    public RawPrtManager(IntPtr rawPtr, int length)
    {
        _rawPtr = rawPtr;
        _length = length;
    }
    public override Span<T> GetSpan()
    {
        Span<T> returnValue;
        unsafe
        {
            returnValue = new Span<T>(_rawPtr.ToPointer(), _length);
        }
        return returnValue;
    }
    protected override void Dispose(bool disposing)
    {
    }
    public override System.Buffers.MemoryHandle Pin(int elementIndex = 0)
    {
        throw new NotSupportedException();
    }
    public override void Unpin()
    {
        throw new NotSupportedException();
    }
}
    public static class af
    {
        [DllImport("afcpu.dll", ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)]
        public static extern int af_create_array(out IntPtr array_arr, [In] float[] data, uint ndims, [In] long[] dim_dims, int type);
        [DllImport("afcpu.dll")]
        public unsafe static extern int af_get_raw_ptr(ref IntPtr ptr, IntPtr arr);
    }
    class Program
    {
        static unsafe void Main(string[] args)
        {
            float[] dotnetArray = {101,2,3,4,5,-11};

            IntPtr af_Ptr = new IntPtr();

            int error = af.af_create_array(out af_Ptr, dotnetArray, 1,new long[] {6},0 );

            IntPtr raw_ptr_ = new IntPtr();

            error = af.af_get_raw_ptr(ref raw_ptr_,af_Ptr);

            var memoryManager = new RawPrtManager<float>(raw_ptr_,6);

            var rawPtrMemory = memoryManager.Memory;

            var dotnetSpan = rawPtrMemory.Span;
        }
    }
dotChris90 commented 5 years ago

@Oceania2018 some other benefit would be the possibility to use System.Numerics.Tensor namespace.

var tensor = new DenseTensor<float>(rawPtrMemory,new int[2]{2,3});
var tensor2 = tensor.Reshape(new int[2]{3,2});
string arrayString = tensor.GetArrayString();

This Tensor could be a base for NumSharps NDArray class (if NDArray is child of DenseTensor). So all this basis things like Reshape, toString and so on can be used by this DenseTensor Class. Moreover (if they really can make it) it was said that Numerics.Tensor classes will be basis for general interop numeric .NET stuff. so NDArrays could be easy interact with other libs like ML.NET.

Oceania2018 commented 5 years ago

@dotChris90 Surprised System.Numerics.Tensor is released. Yes, it's could be the base class of NDArray. Thanks for your information.