Wednesday, November 11, 2015

MKL and C# for dummies (Part 2)



As we saw, it is quite easy to use this library. Obviously you have to search the right function you need marshalling in C# and of course to know something of algebra. Although the esoteric marshalling can create some problem, when you start to face some huge system, big arrays, these functions boost your programs performance.


Memory Limitation (System.OutOfMemoryException)

It is easy to exceed the 2GB RAM .NET memory limit , that involve both x86 and x64 platforms. This was a original choice of Microsoft .NET developer team, the array max size into the heap was 2GB.

By the way, from .NET 4.5 and platform 64-bit it is possible to overcome this limit. But how?
It is enough to add this element gcAllowVeryLargeObjects into the config file App.config.

MSDN Link.

...
  <runtime>
    <gcallowverylargeobjects enabled="true">
  </gcallowverylargeobjects>
  </runtime>

There is always an index limitation, in fact the array index is a represented by Int32 number, so you can have maximum 23997907 items. But it is very difficult to have such big array, while and array like Complex[12000, 12000] is quite normal, enough to overcome 2GB.

Strange Exceptions 
(System.ArgumentException: Array size exceedsadressing limitation)

Every thing works fine now, my class use MKL functions without problems until you reach again 2GB, but why? The Microsoft condition was respected. This was the exception: System.ArgumentException: Array size exceeds dressing limitation!

I spent a lot of time to understand where was the issue, but it seemed nobody knew the solution. The same example I did in C# worked properly if wrote in C++.

At the end I found that the default marshaling doesn't like such large arrays. It was necessary to pin the array and pass a pointer to its first element to the native function. But how?

This was the hint:

using System.Numerics;
using System.Runtime.InteropServices;

public unsafe class MyWrapper
{
    static void Main(string[] args)
    {
        Complex[,] A = new Complex[12000, 12000];

        A[0, 0] = new Complex(0.1, -0.1);
        A[0, 1] = new Complex(11.0, 0.1);
        A[0, 5] = new Complex(55, -0.5);
        A[0, 9] = new Complex(99.0, 0.9);

        fixed (Complex* p = &A[0, 0])
            C_foo(p);
    }

    [DllImport("DLL1.dll", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, SetLastError = false)]
    internal static extern int C_foo(Complex* A);
}

So my class becames:

[SuppressUnmanagedCodeSecurity]
    public unsafe class MKLWrapper
    {

…

 [DllImport("mkl_rt.dll", ExactSpelling = true, SetLastError = false, CallingConvention = CallingConvention.Cdecl)]
        internal static extern int LAPACKE_zgesv(
            int matrix_layout,
            int n,
            int nrhs,
            [In, Out] Complex* A,
            int lda,
            [In, Out] int[] ipiv,
            [In, Out] Complex* B,
            int ldb
        );

public static Complex[,] MKLResolve(Complex[,] MSL, Complex[,] VTN)
        {
            int n = MSL.GetLength(0);
            int nrhs = 1;
            int lda = n;
            int ldb = 1;
            int info = 1;

            int[] ipiv = new int[n];

            fixed (Complex* p1 = &MSL[0,0])
            {
                fixed (Complex* p2 = &VTN[0,0])
                {
                    info = MKLWrapper.LAPACKE_zgesv(LAPACK_ROW_MAJOR, n, nrhs, p1, lda, ipiv, p2, ldb);
                }
            }
            
            if (info == 0)
            {
                //Console.WriteLine("Calculation completed!");               
                return VTN;
            }
            else
            {
                //Console.WriteLine("Calculation Error!");
                return null;
            }
        }

In order to use this sintax the class MKLWrapper need to be declared unsafe:
public unsafe class MKLWrapper


Then the array parameter became:
[In, Out] Complex* A,


And the function needs to be called like this:
fixed (Complex* p1 = &MSL[0,0])
  {
    fixed (Complex* p2 = &VTN[0,0])
    {
      info = MKLWrapper.LAPACKE_zgesv(LAPACK_ROW_MAJOR, n, nrhs, p1, lda, ipiv, p2, ldb);
    }
  }


The fixed statement prevents the garbage collector from relocating a movable variable. The fixed statement is only permitted in an unsafe context. Fixed can also be used to create fixed size buffers. 

Here Microsoft document.

lapack_int 

Another thing to take in account, is that the lapack_int type could be int or long in C# but if declared int, you are sure this declaration is valid between the 32 or 64 bit platforms.


Now it works well!!!!!

Part 1 | Part 2 | Part 3 | Part 4