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

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

测试代码

一维数组的申请和释放

 // Create
int *m = new int[n_row * n_col]; // Free
delete [] m;

二维数组的申请和释放

 // Create
int **m = new int*[n_row];
for ( int i = ; i < n_row; ++i )
m[i] = new int[n_col]; // Free
for ( int i = ; i < n_row; ++i )
delete [] m[i];
delete [] m;

逐行写入

 for ( int i = ; i < n_row; ++i )
{
for ( int j = ; j < n_col; ++j )
{
matrix[i * n_col + j] = answer;
// matrix[i][j] = answer;
}
}

逐列写入

 for ( int j = ; j < n_col; ++j )
{
for ( int i = ; i < n_row; ++i )
{
matrix[i * n_col + j] = answer;
// matrix[i][j] = answer;
}
}

按间隔写入

 for ( int i = ; i < n_row; ++i )
{
for ( int j = ; j < n_col; ++j )
{
int row = i * % n_row;
int col = j * % n_col;
matrix[i * n_col + j] = answer;
// matrix[i][j] = answer;
}
}

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

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

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

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

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

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

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

 // Create
int **m = new int*[n_row];
int *block = new int[n_row * n_col];
for ( int i = ; i < n_row; ++i )
m[i] = &block[i * n_col];
return m;

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

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

测试结果

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

内存的申请和释放

逐行访问

逐列访问

按间隔访问

分析

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

一维数组:

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

二维数组:

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

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

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

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

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

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

  1. 多维矩阵转一维数组(c++)【转载】

    在由二维矩阵转为一维数组时,我们有两种方式:以列为主和以行为主. 以列为主的二维矩阵转为一维数组时,转换公式为: index=column+row×行数 以行为主的二维矩阵转为一维数组时,转换公式为: ...

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

    如题:如何判断一个数组是一维数组或者是二维数组?用什么函数? 判断数量即可 <?php if (count($array) == count($array, 1)) { echo '是一维数组' ...

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

    C#  数组.一维数组.二维数组.多维数组.锯齿数组 一.数组: 如果需要使用同一类型的对象,就可以使用数组,数组是一种数据结构,它可以包含同一类型的多个元素.它的长度是固定的,如长度未知的情况下,请 ...

  4. [PHP]快速实现:将二维数组转为一维数组

    如何将下面的二维数组转为一维数组. $msg = array( array( 'id'=>'45', 'name'=>'jack' ), array( 'id'=>'34', 'na ...

  5. php - 二维数组转一维数组总结

    二维数组转一维数组总结 例如将如下二位数组转以为以为一维数组 $records = [ [ 'id' => 2135, 'first_name' => 'John', 'last_name ...

  6. numpy基础教程--将二维数组转换为一维数组

    1.导入相应的包,本系列教程所有的np指的都是numpy这个包 1 # coding = utf-8 2 import numpy as np 3 import random 2.将二维数组转换为一维 ...

  7. php多维数组化一维数组

    一.使用foreach <?php function arr_foreach ($arr) { static $tmp=array(); if (!is_array ($arr)) { retu ...

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

    //多维数组返回一维数组,拼接字符串输出 public function r_implode( $glue, $pieces ) { foreach( $pieces as $r_pieces ) { ...

  9. numpy 中的reshape,flatten,ravel 数据平展,多维数组变成一维数组

    numpy 中的reshape,flatten,ravel 数据平展,多维数组变成一维数组 import numpy as np 使用array对象 arr1=np.arange(12).reshap ...

随机推荐

  1. luogu P3817 小A的糖果

    题目描述 小A有N个糖果盒,第i个盒中有a[i]颗糖果. 小A每次可以从其中一盒糖果中吃掉一颗,他想知道,要让任意两个相邻的盒子中加起来都只有x颗或以下的糖果,至少得吃掉几颗糖. 输入输出格式 输入格 ...

  2. java.util.List.subList ,开区间和闭区间

    比如集合中的内容为1,2,3,4,5list.sublist(2,4)就返回一个子集合:它的内容包括从下标为2到下标为4,而且这是左闭右开的就是说是从大于等于2到小于4那子集内容就是3,4(集合的下标 ...

  3. sql server 高可用故障转移(1)

    原文:sql server 高可用故障转移(1) 群集准备工作 个人电脑 内存12G,处理器 AMD A6-3650CPU主频2.6GHz 虚拟机 VMware Workstation 12 数据库  ...

  4. linux命令详解:tr命令

    转:http://www.cnblogs.com/lwgdream/archive/2013/11/05/3407809.html 前言 通过tr命令来转化数据,比如大小写的转换:用转换成另外一种字符 ...

  5. Javascript -- document的createDocumentFragment()方法

    在<javascript高级程序设计>一书的6.3.5:创建和操作节点一节中,介绍了几种动态创建html节点的方法,其中有以下几种常见方法: · crateAttribute(name): ...

  6. docker下载ubuntu并进行修改后生成新的镜像提交

    一  docker pull ubuntu ,先下载下来一个镜像, 或者 从本地启动一个镜像 docker run -i -t ubuntu /bin/bash 二 进入一定更新操作 # shell ...

  7. 《深入理解Linux内核》软中断/tasklet/工作队列

    软中断.tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的“下半部”(bottom half)演变而来.下半部的机制实际上包括五种,但2.6版本的内核中,下半部和任 ...

  8. Java高级特性—反射和动态代理

    1).反射 通过反射的方式可以获取class对象中的属性.方法.构造函数等,一下是实例: 2).动态代理 使用场景: 在之前的代码调用阶段,我们用action调用service的方法实现业务即可. 由 ...

  9. rsync数据同步工具的配置

    rsync数据同步工具的配置 1. rsync介绍 1.1.什么是rsync rsync是一款开源的快速的,多功能的,可实现全量及增量的本地或远程数据同步备份的优秀工具.Rsync软件适用于 unix ...

  10. MySQL时间增加、字符串拼接

    MySQL时间增加.字符串拼接 SELECT DATE_ADD(startTime,  INTERVAL 10 SECOND); CONCAT(string1,string2,…)