这可能是一个重复的问题。所以,如果你愿意,请随时将其标记下来。
在C ++中,我了解到数组维度连续存储在内存How are 3D arrays stored in C?中,所以我做了一些实验,将自然数分配给大小为1600000000x1和1x1600000000的矩阵(请将代码中的matsize
更改为较小的值取决于你的记忆)。下面的代码将1到1600000000之间的自然数分配给矩阵a
(其尺寸为1x1600000000),并计算所有元素的立方体总和。相反的情况只是通过将xdim
更改为matsize
和ydim
更改为1来反转矩阵的维度,并重新编译代码并再次运行它。矩阵为[xdim][ydim]
#include <iostream>
#include <time.h>
using namespace std;
int main()
{
long int matsize, i, j, xdim, ydim;
long double ss;
double** a;
double time1, time2, time3;
clock_t starttime = clock();
matsize=1600000000;
xdim=1;
ydim=matsize;
ss=0.0;
a= new double *[xdim];
for(i=0;i<xdim;i++)
{
a[i]= new double[ydim];
}
time1= (double)( clock() - starttime ) / (double)CLOCKS_PER_SEC;
cout << "allocated. time taken for allocation was " << time1 <<" seconds. computation started" << endl;
for(i=0;i<xdim;i++)
{
for(j=0;j<ydim;j++)
{
a[i][j]=(i+1)*(j+1);
ss=ss+a[i][j]*a[i][j]*a[i][j];
}
}
cout << "last number is " << a[xdim-1][ydim-1] << " . sum is " << ss << endl;
time2= ((double)( clock() - starttime ) / (double)CLOCKS_PER_SEC) - time1;
cout << "computation done. time taken for computation was " << time2 << " seconds" << endl;
for(i=0;i<xdim;i++)
{
delete [] a[i];
}
delete [] a;
time3= ((double)( clock() - starttime ) / (double)CLOCKS_PER_SEC) - time2;
cout << "deallocated. time taken for deallocation was " << time3 << " seconds" << endl;
cout << "the total time taken is " << (double)( clock() - starttime ) / (double)CLOCKS_PER_SEC << endl;
cout << "or " << time1+time2+time3 << " seconds" << endl;
return 0;
}
我对两个案例的结果是 -
案例1:xdim = 1且ydim = 1600000000
分配。分配时间为4.5e-05秒。计算开始了 最后一个数字是1.6e + 09。总和是1.6384e + 36 计算完成。计算时间为14.7475秒 释放。释放所需时间为0.875754秒 总时间为15.6233 或15.6233秒
案例2:xdim = 1600000000和ydim = 1
分配。分配时间为56.1583秒。计算开始了 最后一个数字是1.6e + 09。总和是1.6384e + 36 计算完成。计算所需时间为50.7347秒 释放。释放所需时间为270.038秒 总时间为320.773 或376.931秒
两种情况下的输出总和相同。我可以理解在两种情况下分配和释放内存所花费的时间都不同,但是如果内存分配是连续的,为什么计算时间也会有很大不同呢?这段代码有什么问题?
如果重要的话,我在Mountain Lion上使用g ++并使用g ++ -std = c ++ 11,i7四核处理器,16 GB RAM进行编译
答案 0 :(得分:5)
每个单独的向量连续存储内容,包括指向向量的指针向量,但是您对new进行的连续调用的地址不是连续的,也不是内部用于创建缓冲区的调用向量。因此,如果你有一个指向微小向量的巨大指针向量,你的内存实际上是非连续的,你将无法获得良好的缓存命中率。如果你有一个单元素向量到一个巨大的向量,那么内存是连续的,缓存将很好地工作。
视觉上,快速/连续布局是:
*a--[0]
. |
. [0][1][2][3][4][5][...]
你的慢选择是:
. [0] [0]
. \ /
*a--[0][1][2][3][4][5][...]
. | \ | \
. [0] \[0][0] [0]
可以使用例如
在堆栈上创建多维数组int x[10][20];
在这种情况下,存储器将是连续的,x [0],x [1]等中的每个存储器都是连续的。 (所以x [0] [0]在x [0] [1]之前,而不是在x [1] [0]之前。)
要在堆上有效地拥有连续的多维数组,您应该使用预期维度的乘积新建一个向量,然后编写一个包装类,方便地将维度乘以查找特定元素。
答案 1 :(得分:3)
由于数据缓存,计算时间不同。了解locality of reference,当您在内存中读取位置时,CPU会从相邻地址加载数据。它预测下一次读取将来自您刚读取的地址前面几个字节的位置。
当数组被分配为[1][N]
时,元素确实连续存储,因此CPU的预测几乎一直存在。您需要的数据几乎总是可以从CPU的缓存中获得,这比主存储器快几倍。 CPU在执行计算时继续向您刚刚读取的位置前移加载位置,因此加载新数据并将数字相加也是并行的。
当您切换尺寸时,您添加的数字不再位于连续位置。这是因为对new
的连续调用不会在连续的内存区域中分配数据:内存管理库为“簿记”目的添加了几个字节,并且它总是分配一些最小大小的内存块,这通常大于{ {1}}。当您请求小于最小值的块时,将填充分配的区域。结果,在最佳情况 * 中,您的double
最终可能最多相差20个字节 - 足以抵消从相邻内存位置向前读取的影响。这就是为什么CPU在从不同位置加载数据时被迫等待的原因。这会使计算速度减慢几次。