[C++]二维数组还是一维数组?

记得刚学习C++那会这个问题曾困扰过我,后来慢慢形成了不管什么时候都用一维数组的习惯,再后来知道了在一维数组中提出首列元素地址进行二维调用的办法。可从来没有细想过这个问题,最近自己写了点代码测试下,虽然还是有些不明就里,不过结果挺有意思。

为了避免编译器优化过度,用的是写操作,int,测试分为不同大小的空间,同样大小空间不同的行和列数。分别记录逐行写入,逐列写入,按间隔写入,空间申请和释放的时间。

测试代码

一维数组的申请和释放

1 // Create
2 int *m = new int[n_row * n_col];
3
4 // Free
5 delete [] m;

二维数组的申请和释放

1 // Create
2 int **m = new int*[n_row];
3 for ( int i = 0; i < n_row; ++i )
4     m[i] = new int[n_col];
5
6 // Free
7 for ( int i = 0; i < n_row; ++i )
8     delete [] m[i];
9 delete [] m;

逐行写入

1 for ( int i = 0; i < n_row; ++i )
2 {
3     for ( int j = 0; j < n_col; ++j )
4     {
5         matrix[i * n_col + j] = answer;
6         // matrix[i][j] = answer;
7     }
8 }

逐列写入

1 for ( int j = 0; j < n_col; ++j )
2 {
3     for ( int i = 0; i < n_row; ++i )
4     {
5         matrix[i * n_col + j] = answer;
6         // matrix[i][j] = answer;
7     }
8 }

按间隔写入

 1 for ( int i = 0; i < n_row; ++i )
 2 {
 3     for ( int j = 0; j < n_col; ++j )
 4     {
 5         int row = i * 7 % n_row;
 6         int col = j * 11 % n_col;
 7         matrix[i * n_col + j] = answer;
 8         // matrix[i][j] = answer;
 9     }
10 }

不是很确定这种测试是否很合理,不过大体上能体现我要测的东西了。需要注意的是逐行写入中为了简便我用的是写入一个叫answer的变量,其实情况应该更泛化,否则用memset或者std::fill也许会更快?逐列写入的代码本科课程级别的典型反面代码例子,这里也仅仅是为了测试。

仅仅从编译的角度来看主要的差别在下面两句:

1 matrix[i * n_col + j] = answer;
2 matrix[i][j] = answer;

来看一下对应的汇编代码:

1 ; matrix[i * n_col + j] = answer;
2     mov    edx, DWORD PTR _i$3[ebp]
3     imul    edx, DWORD PTR _n_col$[ebp]
4     add    edx, DWORD PTR _j$2[ebp]
5     mov    eax, DWORD PTR _matrix$[ebp]
6     mov    ecx, DWORD PTR _answer$[ebp]
7     mov    DWORD PTR [eax+edx*4], ecx
1 ; matrix[i][j] = answer;
2     mov    ecx, DWORD PTR _i$4[ebp]
3     mov    edx, DWORD PTR _matrix$[ebp]
4     mov    eax, DWORD PTR [edx+ecx*4]
5     mov    ecx, DWORD PTR _j$3[ebp]
6     mov    edx, DWORD PTR _answer$[ebp]
7     mov    DWORD PTR [eax+ecx*4], edx

都是6条指令,体系学得不好,所以也看不出哪个更快。这里用的是Visual Studio编译,因为本人gcc用得不熟,不知道怎么生成这么直观的汇编和C++对应,效率上而言Linux下还是比Windows高一些,不过为了统一,之后的测试也基于Windows。当然上面的是没有优化的编译,如果开了优化(VS /O2),汇编指令大概也都是4、5条的样子,因为/O2优化后的汇编代码结构不是很直观,这里就不贴了。(另一方面也是由于我的汇编水平很弱,看不出什么)

除了直接使用一维和二维数组,也可以对一维数组进行二维索引,方法是把一维数组中所有对应第一列的元素的地址单独作为一个指针数组,这样本质上还是一维数组,但是实现了形式上的二维数组调用,这种办法空间申请的代码如下:

1 // Create
2 int **m = new int*[n_row];
3 int *block = new int[n_row * n_col];
4 for ( int i = 0; i < n_row; ++i )
5     m[i] = &block[i * n_col];
6 return m;

释放的代码和二维数组相同。

用一维数组模拟二维调用的优点是既保证了内存空间的连续性,又保持了二维调用的代码易维护的优点。不过需要注意的是,由于是二维索引,汇编代码和二维数组还是一样的。

测试结果

执行上面的代码,在不同行数和列数下,循环执行取平均值,结果如下:

内存的申请和释放

逐行访问

逐列访问

按间隔访问

分析

内存的连续性:一维数组显然有着比二维数组更好的连续性,我忘了以前是在哪看到的一个形象的字符画说明一维数组和二维数组的区别,大概是下面这样子:

一维数组:

┌--┬--┬--┬--┬-
|  |  |  |  | ...
└--┴--┴--┴--┴-

二维数组:

┌--┬--┬--┬--┬-
|  |  |  |  | ...
└--┴--┴--┴--┴-
  |  |  |
  |  |  V
  |  | ┌--┬--┬--┬-
  |  | |  |  |  | ...
  |  | └--┴--┴--┴-
  |  V
  | ┌--┬--┬--┬--┬-
  | |  |  |  |  | ...
  | └--┴--┴--┴--┴-
  V
 ┌--┬--┬--┬--┬--┬-
 |  |  |  |  |  | ...
 └--┴--┴--┴--┴--┴-

缓存命中率:缓存是SRAM,内存是DRAM,效率差很多,所以如果能提高缓存中的命中率,效率能提高很多。其实这和上一条其实也有关联,显然连续的内存命中率会高,不过如果申请的内存空间非常大那具体问题得具体分析了。

指令执行速度:由于早年体系没学好,所以我也不知道这条有多大影响,另外现在的电脑都是多核的,作为不搞多核算法的人,不太懂会有多大影响。

对照结果可以看到基本上而言一维数组的效率完爆二维数组,尤其是内存申请和小内存访问的情况,总体而言效率上一维数组>一维数组的二维引用>二维数组。不过也有比较有意思的发现:1) 逐行访问的时候,在开辟内存空间小的时候一维数组二维索引效率高于二维数组,而大内存情况下却变慢了。2) 逐列访问基本符合预期,一维数组和一维数组二维索引效率接近,二维索引效率略低,但是都优于二维数组。3) 按间隔访问的时候一维数组大幅快于逐行访问,不太懂这是为什么,是否我电脑是多核的影响?还是说VS的O2编译的作用?

纯属蛋疼的测试,也相当不严谨,希望有体系知识比较丰富的大拿指点一二。

时间: 2024-10-01 07:02:31

[C++]二维数组还是一维数组?的相关文章

数组(一维数组、二维数组)

数组概述 C# 数组从零开始建立索引,即数组索引从零开始.C# 中数组的工作方式与在大多数其他流行语言中的工作方式类似.但还有一些差异应引起注意. 这些其实和泛型有些类似,数组的操作没有泛型方便,但是性能却不是泛型所能比拟的 声明数组时,方括号 ([]) 必须跟在类型后面,而不是标识符后面.在 C# 中,将方括号放在标识符后是不合法的语法. int[] table; // not int table[]; 另一细节是,数组的大小不是其类型的一部分,而在 C 语言中它却是数组类型的一部分.这使您可

将二维数组转为一维数组的2种方法

如何将下面的二维数组转为一维数组. 代码如下: $msg = array( array( 'id'=>'45', 'name'=>'jack' ), array( 'id'=>'34', 'name'=>'mary' ), array( 'id'=>'78', 'name'=>'lili' ), ); 第一种方法: 代码如下: foreach($msg as $k => $v){ $ids[] = $id; $names[] = $name; } 第二种方法: 代

C# 数组、一维数组、二维数组、多维数组、锯齿数组

C#  数组.一维数组.二维数组.多维数组.锯齿数组 一.数组: 如果需要使用同一类型的对象,就可以使用数组,数组是一种数据结构,它可以包含同一类型的多个元素.它的长度是固定的,如长度未知的情况下,请使用集合. 二.一维数组: 声明及初始化: class Program { static void Main(string[] args) { //方法一 int[] num = new int[3];//声明一个长度为3的值类型的数组: num[0] = 3;//为数组赋值: num[1] = 5

把简单的二维数组变成一维数组

在工作中我们经常需要把二维数组变成一维数组.那么就需要循环拿出来,其实有时候我们可以使用一些小技巧,借助php的内置函数来实现,但是我说的只是简单的二维数数组$arr= array('name'=>array('a'=>'abc','b'=>'bcd','c'=>'cde'));如果我们使用array_pop(),就可以拿到一维数组了,array_pop()本来是删除并返回数组的最后一个元素. 所以我们就可以将上诉的$arr变成array('a'=>'abc','b'=>

PHP如何判断一个数组是一维数组或者是二维数组?用什么函数?

如题:如何判断一个数组是一维数组或者是二维数组?用什么函数? 判断数量即可 <?php if (count($array) == count($array, 1)) { echo '是一维数组'; } else { echo '不是一维数组'; } PHP手册: int count ( mixed $var [, int $mode ] )  --  计算数组中的单元数目或对象中的属性个数  如果可选的 mode 参数设为 COUNT_RECURSIVE(或 1),count() 将递归地对数组

随题而学(二)多维数组转一维数组

多维数组转一维数组:[1,[2,3]] ==> [1,2,3] function tran(array){ if(Object.prototype.toString.call(array) != '[object Array]'){ alert(Object.prototype.toString.call(array) + "非数组") return ; } var newArr = []; function trans(arr){ for (var num = 0; num &

php多维数组化一维数组

一.使用foreach <?php function arr_foreach ($arr) { static $tmp=array(); if (!is_array ($arr)) { return false; } foreach ($arr as $val ) { if (is_array ($val)) { arr_foreach ($val); } else { $tmp[]=$val; } } return $tmp; } $a = array(1,2=>array(3,4=>

php - 多维数组转一维数组总结

二维数组转一维数组总结 例如将如下二位数组转以为以为一维数组 $records = [ [ 'id' => 2135, 'first_name' => 'John', 'last_name' => 'Doe', ], [ 'id' => 3245, 'first_name' => 'Sally', 'last_name' => 'Smith', ], [ 'id' => 5342, 'first_name' => 'Jane', 'last_name' =&

implode 多维数组转一维数组并字符串输出

//多维数组返回一维数组,拼接字符串输出 public function r_implode( $glue, $pieces ) { foreach( $pieces as $r_pieces ) { if( is_array( $r_pieces ) ) { $retVal[] = self::r_implode( $glue, $r_pieces ); } else { $retVal[] = $r_pieces; } } return implode( $glue, $retVal );