【发布时间】:2020-11-08 04:53:29
【问题描述】:
#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;
}
所以我正在计算一个 2D 前缀表(总面积表)。我注意到标题中的属性。
使用命令行或 Visual Studio 发布模式使用 CUDA nvcc 编译器(带 -O2)时,结果快 2 倍(单独需要 200 毫秒,打包需要 100 毫秒)第一次运行,但仅后续运行速度提高 25%(这是因为 value2[] 在第一个循环之后被缓存)。在我的具有更多计算步骤的实际程序中(计算 SAT 只是第 1 步),它总是快 2 倍,因为 value1[] 和 value2[] 肯定已从缓存中逐出。
我知道打包数组更快,因为现代 Intel CPU 一次将 32-64 字节读取到缓存中。因此,通过将两个数组打包在一起,它可以在 1 个主内存 (RAM) 访问中读取两个数据,而不是 2 个。但是为什么提速这么高?除了内存访问,CPU 还必须在每个循环中执行 6 次加法、2 次减法和 1 次乘法。将内存访问减半后的 2 倍加速是 100% 的改进效率(Amdahl 定律),就像那些加/乘操作不存在一样。怎么可能?
我确信它与 CPU 流水线有关,但无法更彻底地解释。任何人都可以在指令延迟/内存访问延迟/汇编方面进一步解释这一点吗?谢谢。
代码不使用任何 GPU,因此任何其他好的编译器都应该提供与 nvcc 相同的 2 倍加速。在 g++ 9.3.0 (g++ file.cpp -O2 -std=c++11 -o file.exe) 上,它也是 2 倍加速。 CPU是Intel i7-7700
我已经使用命令行参数-O2 -std=c++11 运行了这个程序here 和here2,它还显示了1.5-2 倍的加速。 使用 n = 3000,更大,它不会运行(毕竟免费的 VM 服务)。所以这不仅仅是我的电脑
【问题讨论】:
-
您是否在启用编译器优化的情况下编译您的代码?如果没有,请不时执行此操作,然后再次进行基准测试。对未优化的调试版本进行基准测试毫无意义。
-
是的,它带有 -O2 以及释放模式。我已经用 nvcc 和 g++ 9.3 对其进行了测试,在这两种情况下,结果都快了 2 倍
-
你的代码中哪里使用了pair?
-
@S.M. -- 在这里:
typedef pair<double,double> pd;-- 但这并不是你的错 -- 这是我最近从很多海报中看到的那些疯狂的宏。不仅类型被宏隐藏,它还被两个字母的小写宏隐藏。 -
得到了不同的基准Here(但必须减小大小以避免超时)
标签: c++ caching memory cpu-registers cpu-cache