C#多维数组的内存布局

多维数组是C#中的一种数据结构。使用多维数组时,不同维度的数组元素在内存中的布局形式是怎样的呢?

下面这一段代码将告诉我们C#中多维数组的内存布局:

            const int size = 10000;

            byte[,] a = new byte[size, size];
            unsafe
            {
                fixed (byte* ptr00 = &a[0, 0], ptr01 = &a[0, 1], ptr10 = &a[1, 0])
                {
                    Console.WriteLine("&a[0,0]={0}", (long)ptr00);
                    Console.WriteLine("&a[0,1]={0}", (long)ptr01);
                    Console.WriteLine("&a[1,0]={0}", (long)ptr10);
                }
            }

在.NET Core 2.1版本中,输出结果为:

&a[0,0]=1549210047680
&a[0,1]=1549210047681
&a[1,0]=1549210057680

可以看到,a[0,0]的地址与a[0,1]相邻,却与a[1,0]不相邻。这说明C#多维数组的最后一个维度是连续排列的。

基于这个结论,理论上遍历一个数组的时候,应该是将最后一个维度作为最内层循环性能较好。我们编写一段代码,观察写入一个二维数组时,两重循环的顺序对执行时间的影响:

            Stopwatch sw = new Stopwatch();
            sw.Reset();
            sw.Start();
            for (int i = 0; i < size; ++i)
            {
                for (int j = 0; j < size; ++j)
                {
                    a[i, j] = (byte)(i + j);
                }
            }
            sw.Stop();
            Console.WriteLine(sw.Elapsed);

            sw.Reset();
            sw.Start();
            for (int j = 0; j < size; ++j)
            {
                for (int i = 0; i < size; ++i)
                {
                    a[i, j] = (byte)(i + j);
                }
            }
            sw.Stop();
            Console.WriteLine(sw.Elapsed);
            // 重复上述代码一遍以减少先后顺序带来的影响

输出结果为:

00:00:01.6993174
00:00:02.5070039
00:00:01.1089673
00:00:02.2817417

这印证了我们刚才得出的结论。

综上所述,C#的多维数组最后一维在内存上是连续排列的,为了提高性能,遍历多维数组时应尽量将最后一个维度放在最内层循环。