在 C++ 中,2D 数组的整体操作是不是比 1D 数组表现得像 2D 数组?

Posted

技术标签:

【中文标题】在 C++ 中,2D 数组的整体操作是不是比 1D 数组表现得像 2D 数组?【英文标题】:In C++, is a 2D array slower in overall operations than a 1D array that behaves like a 2D array?在 C++ 中,2D 数组的整体操作是否比 1D 数组表现得像 2D 数组? 【发布时间】:2020-12-19 02:47:06 【问题描述】:

我只是想问在处理 2D 数组时,在性能和效率方面哪个更好,可能还有代码复杂性,因为我们需要为我们的小组项目制作一个矩阵库。

我在下面有一个代码(我认为这可能不是一个很好的例子,这就是为什么我的内存有限)

#include <iostream>
#include <chrono>

using namespace std;
using namespace std::chrono;

int main()

    auto start = high_resolution_clock::now();
    auto arr2d = new unsigned long long int[10000ul][10000ul];

    int cnt = 1;
    for(unsigned long long int i=0; i<10000ul; ++i)
        for(unsigned long long int j=0; j<10000ul; ++j)
            arr2d[i][j] = cnt;
            cnt++;
        
    
    auto stop = high_resolution_clock::now();   
    auto duration = duration_cast<microseconds>(stop-start);
    cout<<duration.count()<<endl;

    auto start1 = high_resolution_clock::now();
    auto arr1d = new unsigned long long int[100000000ull];

    int cnt1 = 1;
    for(unsigned long long int i=0; i<10000ul; ++i)
        for(unsigned long long int j=0; j<10000ul; ++j)
            arr1d[(i*10000ul)+j] = cnt1;
            cnt1++;
        
    
    auto stop1 = high_resolution_clock::now();  

    auto duration1 = duration_cast<microseconds>(stop1-start1);
    cout<<duration1.count()<<endl;

    return 0;

它表明性能并没有那么大的差异?

谁能给我们一个建议并解释我们应该走哪条路线?

【问题讨论】:

表示大小不会改变,所以这是一个动态数组? uhmmmm...好吧,所以我想静态和动态这个词与我的问题无关,那么我将编辑标题。 uhhhmm...是的,太累了,无法实现它。 这个问题没有单一的答案。具有多维数组的代码的相对性能(相对于以类似于使用二维数组的方式访问一维数组的元素)取决于对数组执行的操作(或访问和修改元素的模式)。对于通用代码——通常使用不同的访问模式来做事情,很难知道——这些操作有时可能适合一维数组,有时适合数组数组。如果循环访问所有元素(即使不是完全顺序),两种方式都没有太大区别 @himynameisjm - 我根本没有建议。这取决于作用于矩阵的算法的性质。对于(例如)矩阵求逆,有一些算法更容易针对一维表示进行优化(例如,端到端连续放置的行或列),但还有其他算法更适合二维数组(即明确表示为数组的数组)。还有一些稀疏矩阵表示不太适合一维或二维表示。当真正的答案是“视情况而定”时,您正在寻找一个通用的答案。 【参考方案1】:

在您的特定情况下,您的两个 for 循环实际上都是按顺序访问内存,即使在二维数组的情况下(由于二维数组的内存布局),所以没有真正的区别。要查看一些不同的执行速度,请尝试以下模式:

for(unsigned long long int i=0; i<10000ull; ++i)
    for(unsigned long long int j=0; j<10000ull; ++j)
        arr2d[j][i] = 1-1*3+3/4;  // note i and j are switched.
    

当您按顺序访问内存时,CPU 足够智能,可以将内存内容预取到 CPU 的 L1 和 L2 缓存中。但是在修改后的版本中,对内存的访问不是顺序的——每次都会提前10000*8字节,CPU不能提前那么远预取,所以代码运行会比较慢。

在您的代码中要注意的另一件事是,表达式1-1*3+3/4 实际上将由编译器预先计算,因为它只包含常量,因此您的代码将仅成为一系列赋值。

【讨论】:

【参考方案2】:

预计这两种方法的性能是相同的。

二维数组,如unsigned long long int[10000ul][10000ul],占用一个连续的内存块,与具有相同元素数量的一维数组相同,如unsigned long long int[100000000ull]。这里没有区别。

着眼于访问数组中的元素,让我们通过假设sizeof(unsigned long long)8 来简化表示法。当您访问arr2d[i][j] 时,编译器会将(i*10000ul + j)*8 添加到arr2d 的起始地址以查找该元素。当您访问arr1d[(i*10000ul)+j] 时,编译器会将((i*10000ul)+j)*8 添加到arr1d 的起始地址以查找该元素。这里没有区别。

这两种方法的低级实现没有区别,所以你应该使用更方便的方法。 但是,在利用此答案之前,请确保您使用的是二维数组(如T [M][N])而不是指向数组的指针数组(如T* [M])。使用一个符号的符号通常与另一个符号无法区分,但是指针数组使用更多内存并且不能保证所有内容都是连续的。这是否会显着影响代码的性能取决于执行了哪些操作。 (虽然这个问题确实显示了一个真正的二维数组,但未来的读者可能会从这个警告中受益。)

说到方便,你可能会发现std::vector 比自己管理内存更方便。但是,如果您选择使用矢量,您可能希望坚持使用 1D 方法。 std::vector&lt; std::vector&lt;unsigned long long int&gt;&gt; 中的嵌套向量类似于指针数组,因此您失去了单个连续内存块的好处。

【讨论】:

以上是关于在 C++ 中,2D 数组的整体操作是不是比 1D 数组表现得像 2D 数组?的主要内容,如果未能解决你的问题,请参考以下文章

按升序排列2d数组中的数字,然后在1d中显示它

Numpy 用 1 列将 1d 重塑为 2d 数组

armadillo C++:无法将浮点二维数组写入 fmat

如何在swift中创建固定大小的2d数组

在 Python 中从两个 1D 数组(2D 图)创建一个 2D 数组(3D 图)(用于计算希尔伯特谱)

值错误:预期的 2D 数组,得到 1D 数组: