Fun with matrix multiplication and unsafe code

In this post I will compare two methods that perform matrix multiplication. We start by defining the Matrix class:

class Matrix
    {
        private readonly double[,] _matrix;
        public Matrix(int dim1, int dim2)
        {
            _matrix = new double[dim1, dim2];
        }

        public int Height { get { return _matrix.GetLength(0); } }
        public int Width { get { return _matrix.GetLength(1); } }

        public double this[int x, int y]
        {
            get { return _matrix[x, y]; }
            set { _matrix[x, y] = value; }
        }
    }

Next we add the first algorithm to the Matrix class which performs a naive multiplication:

 public static Matrix NaiveMultiplication(Matrix m1, Matrix m2)
    {
        Matrix resultMatrix = new Matrix(m1.Height, m2.Width);
        for (int i = 0; i < resultMatrix.Height; i++)
        {
            for (int j = 0; j < resultMatrix.Width; j++)
            {
                resultMatrix[i, j] = 0;
                for (int k = 0; k < m1.Width; k++)
                {
                    resultMatrix[i, j] += m1[i, k] * m2[k, j];
                }
            }
        }
        return resultMatrix;
    }

The second method uses unsafe code:

public unsafe static Matrix UnsafeMultiplication(Matrix m1, Matrix m2)
   {
       int h = m1.Height;
       int w = m2.Width;
       int l = m1.Width;
       Matrix resultMatrix = new Matrix(h, w);
       unsafe
       {
           fixed (double* pm = resultMatrix._matrix, pm1 = m1._matrix, pm2 = m2._matrix)
           {
               int i1, i2;
               for (int i = 0; i < h; i++)
               {
                   i1 = i * l;
                   for (int j = 0; j < w; j++)
                   {
                       i2 = j;
                       double res = 0;
                       for (int k = 0; k < l; k++, i2 += w)
                       {
                           res += pm1[i1 + k] * pm2[i2];
                       }
                       pm[i * w + j] = res;
                   }
               }
           }
       }
       return resultMatrix;
   }

Now it’s time to measure the performance:

class Program
    {
        [DllImport("kernel32.dll")]
        static extern void QueryPerformanceCounter(ref long ticks);

        static long Measure(Action action, int count)
        {
            long startTicks = 0;
            QueryPerformanceCounter(ref startTicks);
            for (int i = 0; i < count; i++)
            {
                action();
            }
            long endTicks = 0;
            QueryPerformanceCounter(ref endTicks);
            return endTicks - startTicks;
        }

        static void Main(string[] args)
        {
            Random random = new Random();

            Matrix m1 = new Matrix(20, 30);
            for (int i = 0; i < m1.Height; i++)
            {
                for (int j = 0; j < m1.Width; j++)
                {
                    m1[i, j] = random.Next(-100, 100);
                }
            }

            Matrix m2 = new Matrix(30, 40);
            for (int i = 0; i < m2.Height; i++)
            {
                for (int j = 0; j < m2.Width; j++)
                {
                    m2[i, j] = random.Next(-100, 100);
                }
            }

            Console.WriteLine(Measure(() => Matrix.NaiveMultiplication(m1, m2), 10000));
            Console.WriteLine(Measure(() => Matrix.UnsafeMultiplication(m1, m2), 10000));
        }
    }

In this test we perform 10000 multiplications of two randomly generated matrices with sizes 20×30 and 30×40 using both methods:

Method CPU cycles
unsafe multiplication 4485698
naive multiplication 58762273

The results show that the naive multiplication is slower by a factor of 13 compared to the multiplication using unsafe code and working directly with memory.

Leave a comment

Your email address will not be published. Required fields are marked *

2 thoughts on “Fun with matrix multiplication and unsafe code”