As every .NET developer knows memory usage is managed from Garbage Collector. This layer determines when memory is released and how to reorganize it. It allocates spaces for each thread separately and avoid conflicts.
For this, we programmers often don’t know exactly what really happen at this level, the details.
In general, this is enough, because GC has been built in order to permit developer to concentrate at higher levels.
But sometimes, especially when you pass pointers to memory block, like array to API functions or specifically to MKL API functions, it is important to know what happen under the scene.
IntPtr x = new IntPtr(0); double x_init = null; x = mkl_malloc(sizeof(double) * n, 64); Marshal.Copy(x_init, 0, x, n); //use x pointer … mkl_free(ref x);
This set of instructions define a memory space for an array of double and assign a memory pointer to x. This pointer x is then passed to a function like this:
[DllImport("mkl_rt.dll", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, SetLastError = false)] internal static extern int dtrnlspbc_init( ref IntPtr handle, ref int n, ref int m, IntPtr x, IntPtr LW, IntPtr UP, double eps, ref int iter1, ref int iter2, ref double rs );
Everything seems to work, but this is a very subtle felling!
Yes, because memory is in the heap area managed by GC and can be moved, reorganized. This means that your pointer x is not reliable.
We spend a lot of time to fight against strange results, sometimes good, sometimes not. Where was the trick? Was Intel fault of our fault? I don’t like this word “fault”, but the question was where the issue?
At the end, we got the solution!
This was: Pin the pointer with the correct syntax and methods.
GCHandle x_handle = GCHandle.Alloc(x_init, GCHandleType.Pinned); x = x_handle.AddrOfPinnedObject(); //use x pointer … x_handle.Free();
Now GC can’t move the pointer and the array used by API function is always located.
I hope these notes useful for poor developer always alone in the ocean of troubles.
Actually, problem in above code was not with x, but with eps. Since IntPtr is blittable type, it is passed by value to unmanaged code. But an array of blittable type, such as double , is pinned in memory and passed by reference. However, the pinning in memory is only for the duration of the call to unmanaged code, and this is where the trouble starts. dtrnlspbs_init stores pointers to x, LW, UP, and eps in its handle, and these are later accessed by dtrnlspbs_solve through the same handle. While x has been stored in memory that is controlled by MKL, eps is stored in managed memory, which can be moved around by the Garbage Collector. Since dtrnlspbs_solve is called without eps as an argument, the Garbage Collector is free to move it around in the meantime. The solution is the same as described above: use GCHandle to create a handle that explicitly pins eps in memory until we are done with it. Thanks to Gianluca for sharing his result!
GCHandle eps_handle = GCHandle.Alloc(eps, GCHandleType.Pinned); // ... // calls to unmanaged code that access eps indirectly // ... eps_handle.Free();
Thanks for your post! I was directed to it by Ying H. (Intel) in response to my call for help. I am calling DSS (Direct Sparse Solver) from managed code, and memory violation happened once in a while. This information opened my eyes. Pinning objects completely solved the problem!
Hi Maria, Gianluca,
Very to glad to know your problem are solved completely. We will also plan to add some example in MKL C# sample articles to show the case so more .net developer will benefit.
Thank you too very much for discovering the problem