C#中的多维数组double[,]和数组数组double[][]什么区别?

如果存在差异,那么每个最好的用途是什么?


#1楼

前言:此评论旨在解决okutane提供的答案 ,但是由于SO的愚蠢信誉系统,我无法将其张贴在它所属的位置。

您断言一个人由于方法调用而比另一个人慢,这是不正确的。 一种是慢于另一种,因为边界检查算法更加复杂。 您可以通过查看而不是IL而不是IL来轻松地验证这一点。 例如,在我的4.5安装中,访问存储在ecx指向的二维数组中的元素(通过edx中的指针),并在eax和edx中存储索引,如下所示:

sub eax,[ecx+10]
cmp eax,[ecx+08]
jae oops //jump to throw out of bounds exception
sub edx,[ecx+14]
cmp edx,[ecx+0C]
jae oops //jump to throw out of bounds exception
imul eax,[ecx+0C]
add eax,edx
lea edx,[ecx+eax*4+18]

在这里,您可以看到方法调用没有任何开销。 由于非零索引的可能性,边界检查非常复杂,这是锯齿状数组不提供的功能。 如果删除非零情况下的sub,cmp和jmps,则代码几乎解析为(x*y_max+y)*sizeof(ptr)+sizeof(array_header) 。 这种计算与随机访问元素的速度差不多一样快(一个乘法可以由一个移位代替,因为这就是我们选择将字节大小设置为两位的幂的全部原因)。

另一个复杂因素是,在很多情况下,现代编译器将优化嵌套嵌套边界检查元素访问,同时在单维数组上进行迭代。 结果是代码基本上只将索引指针移到数组的连续内存上。 多维数组上的幼稚迭代通常涉及嵌套逻辑的额外层,因此编译器不太可能优化操作。 因此,即使访问单个元素的边界检查开销在数组维数和大小方面均摊到了恒定的运行时间上,但用于测量差异的简单测试用例执行起来却可能会花费很多时间。


#2楼

上面的答案中可能已经提到了这一点,但没有明确提及:对于锯齿状数组,可以使用array[row]引用整行数据,但是对于multi-d数组则不允许这样做。


#3楼

我正在解析ildasm生成的.il文件,以建立一个包含组件,类,方法和存储过程的数据库,以进行转换。 我遇到了以下内容,这破坏了我的解析。

.method private hidebysig instance uint32[0...,0...] GenerateWorkingKey(uint8[] key,bool forEncryption) cil managed

Serge Lidin,Apress出版的《 Expert .NET 2.0 IL汇编器》一书于2006年出版,第8章,原始类型和签名,第149-150页。

<type>[]被称为<type>的向量,

<type>[<bounds> [<bounds>**] ]被称为<type>的数组

**表示可以重复, [ ]表示可选。

示例:让<type> = int32

1) int32[...,...]是未定义下限和大小的二维数组

2) int32[2...5]是下界2和大小4的一维数组。

3) int32[0...,0...]是一个下限为0和未定义大小的二维数组。

汤姆


#4楼

除了其他答案,请注意,多维数组被分配为堆上的一个大块对象。 这具有一些含义:

  1. 一些多维数组将在大对象堆(LOH)上分配,否则它们对应的锯齿数组副本将没有。
  2. GC将需要找到单个连续的空闲内存块来分配多维数组,而锯齿状的数组可能能够填补由堆碎片引起的空白...由于压缩,这在.NET中通常不是问题,但默认情况下不会对LOH进行压缩(您必须提出要求,并且每次都需要询问)。
  3. 如果只使用锯齿状数组,您将需要研究<gcAllowVeryLargeObjects>多维数组的方式,然后问题才会出现。

#5楼

我想对此进行更新,因为在.NET Core中,多维数组比锯齿数组要快 。 我运行了John Leidegren的测试,这些是.NET Core 2.0 Preview 2上的结果。我增加了维度值,以使来自后台应用程序的任何可能影响都不明显。

Debug (code optimalization disabled)
Running jagged
187.232 200.585 219.927 227.765 225.334 222.745 224.036 222.396 219.912 222.737 Running multi-dimensional
130.732 151.398 131.763 129.740 129.572 159.948 145.464 131.930 133.117 129.342 Running single-dimensional  91.153 145.657 111.974  96.436 100.015  97.640  94.581 139.658 108.326  92.931 Release (code optimalization enabled)
Running jagged
108.503 95.409 128.187 121.877 119.295 118.201 102.321 116.393 125.499 116.459 Running multi-dimensional 62.292  60.627  60.611  60.883  61.167  60.923  62.083  60.932  61.444  62.974 Running single-dimensional 34.974  33.901  34.088  34.659  34.064  34.735  34.919  34.694  35.006  34.796

我调查了拆卸过程,这就是我发现的

jagged[i][j][k] = i * j * k; 需要34条指令才能执行

multi[i, j, k] = i * j * k; 需要11条指令才能执行

single[i * dim * dim + j * dim + k] = i * j * k; 需要23条指令才能执行

我无法确定为什么一维数组仍然比多维数组快,但是我的猜测是它与CPU上的某些优化有关


#6楼

数组数组(锯齿状数组)比多维数组更快,并且可以更有效地使用。 多维数组具有更好的语法。

如果您使用锯齿形和多维数组编写一些简单的代码,然后使用IL反汇编程序检查编译后的程序集,您会发现从锯齿形(或一维)数组进行存储和检索是简单的IL指令,而多维数组的相同操作是方法调用总是比较慢。

请考虑以下方法:

static void SetElementAt(int[][] array, int i, int j, int value)
{array[i][j] = value;
}static void SetElementAt(int[,] array, int i, int j, int value)
{array[i, j] = value;
}

他们的IL如下:

.method private hidebysig static void  SetElementAt(int32[][] 'array',int32 i,int32 j,int32 'value') cil managed
{// Code size       7 (0x7).maxstack  8IL_0000:  ldarg.0IL_0001:  ldarg.1IL_0002:  ldelem.refIL_0003:  ldarg.2IL_0004:  ldarg.3IL_0005:  stelem.i4IL_0006:  ret
} // end of method Program::SetElementAt.method private hidebysig static void  SetElementAt(int32[0...,0...] 'array',int32 i,int32 j,int32 'value') cil managed
{// Code size       10 (0xa).maxstack  8IL_0000:  ldarg.0IL_0001:  ldarg.1IL_0002:  ldarg.2IL_0003:  ldarg.3IL_0004:  call       instance void int32[0...,0...]::Set(int32,int32,int32)IL_0009:  ret
} // end of method Program::SetElementAt

使用锯齿状数组时,您可以轻松执行诸如行交换和行调整大小之类的操作。 也许在某些情况下使用多维数组会更安全,但是即使Microsoft FxCop告诉您,在使用锯齿状数组分析项目时也应使用锯齿状数组,而不是多维数组。


#7楼

简单地说,多维数组类似于DBMS中的表。
Array of Array(锯齿状数组)使您可以让每个元素容纳另一个相同类型的可变长度数组。

因此,如果您确定数据结构看起来像表格(固定的行/列),则可以使用多维数组。 锯齿形数组是固定元素,每个元素可以容纳可变长度的数组

例如Psuedocode:

int[,] data = new int[2,2];
data[0,0] = 1;
data[0,1] = 2;
data[1,0] = 3;
data[1,1] = 4;

将以上内容视为2x2表格:

 1 | 2 3 | 4 
int[][] jagged = new int[3][];
jagged[0] = new int[4] {  1,  2,  3,  4 };
jagged[1] = new int[2] { 11, 12 };
jagged[2] = new int[3] { 21, 22, 23 };

将以上内容视为每一行具有可变的列数:

  1 | 2 | 3 | 4 11 | 12 21 | 22 | 23 

#8楼

多维数组创建了一个不错的线性内存布局,而锯齿状数组则暗示了额外的间接级别。

在锯齿状数组中查找值jagged[3][6] var jagged = new int[10][5]工作方式如下:查找索引3处的元素(这是一个数组)并查找索引处的元素该数组中的6(这是一个值)。 对于这种情况下的每个维度,都需要进行额外的查找(这是一种昂贵的内存访问模式)。

多维数组线性排列在内存中,通过将索引相乘即可找到实际值。 但是,给定数组var mult = new int[10,30] ,该多维数组的Length属性返回元素总数,即10 * 30 = 300。

锯齿状数组的Rank属性始终为1,但是多维数组可以具有任何秩。 任何数组的GetLength方法都可用于获取每个维度的长度。 对于此示例中的多维数组, mult.GetLength(1)返回30。

索引多维数组更快。 例如,在本例中,给定多维数组mult[1,7] = 30 * 1 + 7 = 37,得到该索引处的元素37。这是一种更好的内存访问模式,因为仅涉及一个内存位置,这是基数数组的地址。

因此,多维数组分配一个连续的存储块,而锯齿数组不必是正方形的,例如jagged[1].Length不必等于jagged[2].Length ,这对于任何多维数组都是正确的。

性能

在性能方面,多维数组应该更快。 速度快了很多,但是由于CLR实施的确很差,所以不是。

 23.084  16.634  15.215  15.489  14.407  13.691  14.695  14.398  14.551  14.252 25.782  27.484  25.711  20.844  19.607  20.349  25.861  26.214  19.677  20.171 5.050   5.085   6.412   5.225   5.100   5.751   6.650   5.222   6.770   5.305

第一行是锯齿状数组的时间,第二行是多维数组,第三行是应该的。 该程序如下所示,仅供参考,这是经过测试运行的单声道。 (Windows的时间差异很大,主要是由于CLR实现的变化)。

在Windows上,锯齿状数组的时序非常优越,与我自己对多维数组查找的解释应该是相同的,请参见“ Single()”。 遗憾的是,Windows JIT编译器确实很愚蠢,这不幸地使这些性能讨论变得困难,存在太多的不一致之处。

这些是我在Windows上获得的时间,在这里也是一样,第一行是锯齿状的数组,第二排是多维的,第三排是我自己的多维实现,请注意,与mono相比,这在Windows上要慢得多。

  8.438   2.004   8.439   4.362   4.936   4.533   4.751   4.776   4.635   5.8647.414  13.196  11.940  11.832  11.675  11.811  11.812  12.964  11.885  11.75111.355  10.788  10.527  10.541  10.745  10.723  10.651  10.930  10.639  10.595

源代码:

using System;
using System.Diagnostics;
static class ArrayPref
{const string Format = "{0,7:0.000} ";static void Main(){Jagged();Multi();Single();}static void Jagged(){const int dim = 100;for(var passes = 0; passes < 10; passes++){var timer = new Stopwatch();timer.Start();var jagged = new int[dim][][];for(var i = 0; i < dim; i++){jagged[i] = new int[dim][];for(var j = 0; j < dim; j++){jagged[i][j] = new int[dim];for(var k = 0; k < dim; k++){jagged[i][j][k] = i * j * k;}}}timer.Stop();Console.Write(Format,(double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);}Console.WriteLine();}static void Multi(){const int dim = 100;for(var passes = 0; passes < 10; passes++){var timer = new Stopwatch();timer.Start();var multi = new int[dim,dim,dim];for(var i = 0; i < dim; i++){for(var j = 0; j < dim; j++){for(var k = 0; k < dim; k++){multi[i,j,k] = i * j * k;}}}timer.Stop();Console.Write(Format,(double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);}Console.WriteLine();}static void Single(){const int dim = 100;for(var passes = 0; passes < 10; passes++){var timer = new Stopwatch();timer.Start();var single = new int[dim*dim*dim];for(var i = 0; i < dim; i++){for(var j = 0; j < dim; j++){for(var k = 0; k < dim; k++){single[i*dim*dim+j*dim+k] = i * j * k;}}}timer.Stop();Console.Write(Format,(double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);}Console.WriteLine();}
}

#9楼

多维数组是(n-1)维矩阵。

所以int[,] square = new int[2,2]是方阵2x2, int[,,] cube = new int [3,3,3]是立方体-方阵3x3。 不需要比例。

锯齿状的数组只是数组的数组-每个单元格都包含一个数组的数组。

所以MDA是成比例的,JD可能不是! 每个单元格可以包含任意长度的数组!

多维数组和C#中的数组数组有什么区别?相关推荐

  1. php中声明空数组,总结PHP中初始化空数组的最佳方法

    PHP支持三种类型的数组: 索引数组:具有数字索引的数组. 关联数组:具有命名键的数组. 多维数组:它包含特定数组中的一个或多个数组. 注意:为什么声明一个空数组然后将项目推送到该数组总是好的做法? ...

  2. matlab创建元胞数组对象,MATLAB中胞元数组的用法

    胞元数组(cell Arry)的基本组分是胞元(cell),每个胞元本身在数组中是平等的,只能以下标区分.胞元可以存放任何类型.任何大小的数组,如任意维数值数组.字符串数组.符号对象等,而且同一个胞元 ...

  3. matlab 数组差分,matlab中计算三维数组的差分

    满意答案 dqnic2013 2016.04.15 采纳率:50%    等级:7 已帮助:411人 (1)在Matlab中习惯性的会将二维数组中的第一维称为"行"第二维称为&qu ...

  4. java 中数组与list_Java中List与数组相互转换实例分析

    这篇文章主要介绍了Java中List与数组相互转换的方法,实例分析了Java中List与数组相互转换中容易出现的问题与相关的解决方法,具有一定参考借鉴价值,需要的朋友可以参考下 本文实例分析了Java ...

  5. python的列表就是数组吗_python中list和数组的区别是什么?

    在python中,list和数组的区别是:1.list中的元素的数据类型可以不一样,数组中的元素的数据类型必须一样:2.list不可以进行四则运算,数组可以进行四则运算. list和array的区别 ...

  6. java arraylist与数组转换_java中String,数组,ArrayList三者之间的转换

    免费资源网 - https://freexyz.cn/ ArrayList转为String和数组 造一个元素为Integer的ArrayList 为list_int 造一个元素为String 的 Ar ...

  7. html数组显示,javascript中怎么输出数组?

    作为一个程序员对于数组遍历大家都不是很陌生,在开发中我们也经常要处理数组.下面我们来看一下JavaScript如何输出数组. JavaScript中可以通过循环遍历数组,在循环中使用document. ...

  8. Java 数组在内存中的存储 数组的常见操作

    Java虚拟机的内存划分 为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式. JVM的内存划分: 区域名称 作用 寄存器 给CPU使用,和我们开发无关 ...

  9. php怎么比较数组长度_PHP中如何获取数组的长度

    编程中数组的使用频率是非常高的,其它判断一个数据是否为空,我们通常都会判断数组的长度.这篇文章就说说在PHP编程语言中如果获取一个数组的长度. PHP count()函数 count():函数返回数组 ...

  10. matlab返回数组下标,matlab中如何将数组下标定义为从0开始

    楼主不是想保留0:3这个信息?如果是的话,可以用结构数据来保存,或者是令k=0:3,然后显示的时候为t(k+1)不就行了.又或者编个函数,转换一下www.mh456.com防采集. 这个是没法直接修改 ...

最新文章

  1. linux环境下解决字体乱码的问题
  2. 报名开启!AI+科学计算专场直播:大咖齐聚×独家礼物,限量200份
  3. 基于MATLAB的图像压缩感知设计(含源文件)
  4. 量子时代已来,与时代接轨,从这本书开始
  5. 大学生必看的一分钟——俞洪敏语录
  6. 钉钉人脸识别,戴个太阳帽就找不到人脸
  7. 某职业院校二级学院的发展规划
  8. 关于ADC采样的采样频率,采样时间的问题
  9. excel表格操作之数据分级统计
  10. Xdebug中文文档-堆栈跟踪
  11. C语言实现人物动态移动效果
  12. 诗词大全给力版_热卖20万套的网红古诗词日历来了!全新开启2021诗意生活
  13. 宜信唐宁:个人投资者做好资产配置至关重要
  14. 用户出示二维码向商家付钱
  15. 字符编码在项目中的应用(一)
  16. 舞台音效控制软件_苹果舞台现场演奏音效控制工具 MainStage 3 v3.3.1
  17. 什么是白皮书?【理解较局限,仅个人学习记录】
  18. 惯性导航和惯性器件(三)
  19. OpenCV系列之用于角点检测的FAST算法 | 四十一
  20. ARM(Advanced RISC Machines)

热门文章

  1. NotePad++ 宏录制使用
  2. adb logcat read: unexpected EOF!
  3. Android getLocationInWindow
  4. 高大上的Android沉浸式状态栏?
  5. 第十六周项目一-小玩文件(2)
  6. JavaScript语言基础7
  7. (008) java后台开发之java程序中的控制结构
  8. webpack的使用、安装和配置打包的入口和出口
  9. 递归删除N天前的文件夹及子文件夹下的特定文件
  10. Java编程之前的复习和练习