pair<double,double> 的数组如何比两个双 C++ 数组快 2 倍

How is array of pair<double,double> 2 times faster than two arrays of double C++

#include <iostream>
#include <chrono>
#include <random>
#include <time.h>
using namespace std;

typedef pair<double,double> pd;
#define x first
#define y second
#define cell(i,j,w) ((i)*(w) + (j))

class MyTimer
{
private:
    std::chrono::time_point<std::chrono::steady_clock> starter;
    std::chrono::time_point<std::chrono::steady_clock> ender;

public:
    void startCounter() {
        starter = std::chrono::steady_clock::now();
    }

    long long getCounter() {
        ender = std::chrono::steady_clock::now();
        return std::chrono::duration_cast<std::chrono::milliseconds>(ender - starter).count();
    }
};
        
int main()
{
    const int n = 5000;
    int* value1 = new int[(n + 1) * (n + 1)];
    int* value2 = new int[(n + 1) * (n + 1)];
    double* a = new double[(n + 1) * (n + 1)];
    double* b = new double[(n + 1) * (n + 1)];
    pd* packed = new pd[(n + 1) * (n + 1)];
    MyTimer timer;

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) {
            value1[cell(i, j, n + 1)] = rand() % 5000;
            value2[cell(i, j, n + 1)] = rand() % 5000;
        }

    for (int i = 1; i <= n; i++) {
        a[cell(i, 0, n + 1)] = 0;
        a[cell(0, i, n + 1)] = 0;
        b[cell(i, 0, n + 1)] = 0;
        b[cell(0, i, n + 1)] = 0;
        packed[cell(i, 0, n + 1)] = pd(0, 0);
        packed[cell(0, i, n + 1)] = pd(0, 0);
    }

    for (int tt=1; tt<=5; tt++)
    {
        timer.startCounter();
        for (int i=1; i<=n; i++)
            for (int j = 1; j <= n; j++) {
                // packed[i][j] = packed[i-1][j] + packed[i][j-1] - packed[i-1][j-1] + value1[i][j]
                packed[cell(i, j, n + 1)].x = packed[cell(i - 1, j, n + 1)].x + packed[cell(i, j - 1, n + 1)].x - packed[cell(i - 1, j - 1, n + 1)].x + value1[cell(i, j, n + 1)];
                packed[cell(i, j, n + 1)].y = packed[cell(i - 1, j, n + 1)].y + packed[cell(i, j - 1, n + 1)].y - packed[cell(i - 1, j - 1, n + 1)].y + value1[cell(i, j, n + 1)] * value1[cell(i, j, n + 1)];
            }
        cout << "Time packed  = " << timer.getCounter() << "\n";

        timer.startCounter();
        for (int i=1; i<=n; i++)
            for (int j = 1; j <= n; j++) {
                // a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + value2[i][j];
                // b[i][j] = b[i-1][j] + b[i][j-1] - b[i-1][j-1] + value2[i][j] * value2[i][j];
                a[cell(i, j, n + 1)] = a[cell(i - 1, j, n + 1)] + a[cell(i, j - 1, n + 1)] - a[cell(i - 1, j - 1, n + 1)] + value2[cell(i, j, n + 1)];
                b[cell(i, j, n + 1)] = b[cell(i - 1, j, n + 1)] + b[cell(i, j - 1, n + 1)] - b[cell(i - 1, j - 1, n + 1)] + value2[cell(i, j, n + 1)] * value2[cell(i, j, n + 1)];
            }
        cout << "Time separate = " << timer.getCounter() << "\n\n";
    }

    delete[] value1;
    delete[] value2;
    delete[] a;
    delete[] b;
    delete[] packed;
}

所以我正在计算二维前缀 table(总面积 Table)。我注意到标题中的 属性。

当使用命令行或Visual Studio发布模式使用 CUDA nvcc 编译器(带 -O2)时,结果快 2 倍(单独需要 200 毫秒,打包需要100 毫秒)第一个 运行,但在随后的 运行 中只快 25%(这是因为 value2[] 在第一个循环之后被缓存)。在我的计算步骤更多的实际程序中(计算 SAT 只是第 1 步),它总是快 2 倍,因为 value1[]value2[] 肯定已从缓存中逐出。

我知道打包数组更快,因为现代英特尔 CPU 一次将 32-64 字节读入缓存 。因此,通过将两个数组打包在一起,它可以 在 1 个主存储器 (RAM) 访问而不是 2 中读取两个数据。但是为什么加速比这么高?除了内存访问,CPU 仍然必须在每个循环中执行 6 次加法、2 次减法和 1 次乘法。将内存访问减半的 2 倍加速是 100% 的效率提升(阿姆达尔定律),与那些 add/mult 操作 没有一样存在。怎么可能?

我确定它与 CPU 流水线有关,但无法解释得更透彻。谁能根据指令 latency/memory 访问 latency/assembly 进一步解释这一点?谢谢。

该代码不使用任何 GPU,因此任何其他好的编译器应该提供与 nvcc 相同的 2 倍加速。在 g++ 9.3.0 (g++ file.cpp -O2 -std=c++11 -o file.exe) 上,它也是 2 倍加速。 CPU 是英特尔 i7-7700

我 运行 这个程序 here and here2 带有命令行参数 -O2 -std=c++11,它还显示了 1.5-2 倍的加速。 使用 n = 3000,再大也不会 运行(毕竟免费的 VM 服务)。所以不只是我的电脑

答案在于不同级别内存的访问延迟,从 L1 缓存 -> 主内存 (RAM)。

L1 缓存中的数据需要~~5 个周期才能访问,而来自 RAM 的数据需要 50-100 个周期。同时,add/sub/mult 操作需要 3-5 个周期。

因此,性能的主要限制因素是主内存访问。所以通过将主内存请求的数量减少一半,性能几乎翻倍