随笔 - 139  文章 - 0  评论 - 421  阅读 - 17万

C#中各种数组的性能比较

在C#中提供了一维数组,多维数组,和交错数组(也叫齿状数组Jagged Array),由于C#本身并不直接支持非0基(0基的意思是数组的最小索引是0)的数组(虽然可以通过Array.CreateInstance()方法在C#中创建非0基数组),而且CLS(通用语言规范)也并不支持非0基数组,所以这篇文章中不讨论非0基数组。
1,一维0基数组:
   一般情况下,建议大家最好使用一维0基数组,在C#直接支持的数组中,这种类型的数组性能最好,因为在IL中直接提供了特殊的指令来操作一维0基数组,大家看看如下的代码:
   int[] arr = new int[5];
   int i = arr[3];
   这两行代码生成的IL是这样的:
  .entrypoint
  // 代码大小       13 (0xd)
  .maxstack  2
  .locals init ([0] int32[] arr,
           [1] int32 i)
  IL_0000:  nop
  IL_0001:  ldc.i4.5
  IL_0002:  newarr     [mscorlib]System.Int32
  IL_0007:  stloc.0
  IL_0008:  ldloc.0
  IL_0009:  ldc.i4.3
  IL_000a:  ldelem.i4
  IL_000b:  stloc.1
  IL_000c:  ret
大家可以看到IL提供了newarr指令来新建一个数组,ldelem.i4指令来获取Int32类型的数组元素,此外IL还6提供了其他的指令来操作一维0基数组:
ldelema   :读取一维0基数组中一个元素的地址

ldlen     :读取一维0基数组中的元素个数

stelem    :为数组中的一个元素赋值

ldelem.ref:则用于操作所有包含引用类型对象的数组

2,交错数组
   交错数组实际上就是数组的数组,也就是说交错数组中的每个元素本身又是一个数组,每个嵌套数组并不要求长度一致,大家看如下代码:

            int[][] arr = new int[5][];
            arr[0] = new int[3];
            arr[1] = new int[4];
            arr[2] = new int[5];
            arr[3] = new int[6];
            arr[4] = new int[7];

            int i=arr[3][4];

生成的IL代码如下:
  .entrypoint
  // 代码大小       60 (0x3c)
  .maxstack  3
  .locals init ([0] int32[][] arr,
           [1] int32 i)
  IL_0000:  nop
  IL_0001:  ldc.i4.5
  IL_0002:  newarr     int32[]
  IL_0007:  stloc.0
  IL_0008:  ldloc.0
  IL_0009:  ldc.i4.0
  IL_000a:  ldc.i4.3
  IL_000b:  newarr     [mscorlib]System.Int32
  IL_0010:  stelem.ref
  IL_0011:  ldloc.0
  IL_0012:  ldc.i4.1
  IL_0013:  ldc.i4.4
  IL_0014:  newarr     [mscorlib]System.Int32
  IL_0019:  stelem.ref
  IL_001a:  ldloc.0
  IL_001b:  ldc.i4.2
  IL_001c:  ldc.i4.5
  IL_001d:  newarr     [mscorlib]System.Int32
  IL_0022:  stelem.ref
  IL_0023:  ldloc.0
  IL_0024:  ldc.i4.3
  IL_0025:  ldc.i4.6
  IL_0026:  newarr     [mscorlib]System.Int32
  IL_002b:  stelem.ref
  IL_002c:  ldloc.0
  IL_002d:  ldc.i4.4
  IL_002e:  ldc.i4.7
  IL_002f:  newarr     [mscorlib]System.Int32
  IL_0034:  stelem.ref
  IL_0035:  ldloc.0
  IL_0036:  ldc.i4.3
  IL_0037:  ldelem.ref
  IL_0038:  ldc.i4.4
  IL_0039:  ldelem.i4
  IL_003a:  stloc.1
  IL_003b:  ret

代码比较长,前面基本都是初始化的代码,大家不用太关心,但是大家明显能看到一维0基的交错数组同样也可以利用IL为一维0基数组提供的特殊指令,所以性能和一维0基数组相同,唯一的不同是多了一次嵌套数组的访问!
   
3,多维数组:
   多维数组一般大家可能直观上的认为多维数组的性能也应该不错,因为而多维数组当中,由于用于存储元素的内存空间是连续的,而且数组的每一维元素个数固定,所以可以轻易的根据元素各维的索引值计算出元素在数组内存中偏移量,比如,一个多维数组arr[6,7],元素arr[2,4]的偏移量是2*7+4,事实证明这种直观看法并不正确,我们看如下代码:

int[,] arr = new int[5,6];
int i = arr[3, 4];

生成的IL代码如下:
  .entrypoint
  // 代码大小       19 (0x13)
  .maxstack  3
  .locals init ([0] int32[0...,0...] arr,
           [1] int32 i)
  IL_0000:  nop
  IL_0001:  ldc.i4.5
  IL_0002:  ldc.i4.6
  IL_0003:  newobj     instance void int32[0...,0...]::.ctor(int32,
                                                             int32)
  IL_0008:  stloc.0
  IL_0009:  ldloc.0
  IL_000a:  ldc.i4.3
  IL_000b:  ldc.i4.4
  IL_000c:  call       instance int32 int32[0...,0...]::Get(int32,
                                                            int32)
  IL_0011:  stloc.1
  IL_0012:  ret

大家都知道,数组是继承于Array的引用类型,这里int32[0...,0...]类型就是CLR为多维数组构造的一个类型,Get(int32,int32)方法是CLR为这个类型构造的一个方法,类似的方法还有:

int Get(int d1, int d2);   //获取特定元素的值

void Set(int d1, int d2, int v); //设置特定元素的值

int* Address(int d1, int d2);    //获取特定元素的地址

函数调用本身是比较耗时的,因为它包含了参数的压栈出栈,以及程序控制流的转移等,显然,多维数组的这种通过函数调用来访问元素的方式没有一维0基的交错数组直接使用IL指令的方式快!



  
posted on   周雪峰  阅读(5403)  评论(3编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述
< 2010年3月 >
28 1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31 1 2 3
4 5 6 7 8 9 10

点击右上角即可分享
微信分享提示