【发布时间】:2021-04-11 17:09:29
【问题描述】:
所以最近我遇到了一个我认为很有趣但无法完全解释的问题。我在以下代码中强调了问题的性质:
#include <cstring>
#include <chrono>
#include <iostream>
#define NLOOPS 10
void doWorkFast(int total, int *write, int *read)
{
for (int j = 0; j < NLOOPS; j++) {
for (int i = 0; i < total; i++) {
write[i] = read[i] + i;
}
}
}
void doWorkSlow(int total, int *write, int *read, int innerLoopSize)
{
for (int i = 0; i < NLOOPS; i++) {
for (int j = 0; j < total/innerLoopSize; j++) {
for (int k = 0; k < innerLoopSize; k++) {
write[j*k + k] = read[j*k + k] + j*k + k;
}
}
}
}
int main(int argc, char *argv[])
{
int n = 1000000000;
int *heapMemoryWrite = new int[n];
int *heapMemoryRead = new int[n];
for (int i = 0; i < n; i++)
{
heapMemoryRead[i] = 1;
}
std::memset(heapMemoryWrite, 0, n * sizeof(int));
auto start1 = std::chrono::high_resolution_clock::now();
doWorkFast(n,heapMemoryWrite, heapMemoryRead);
auto finish1 = std::chrono::high_resolution_clock::now();
auto duration1 = std::chrono::duration_cast<std::chrono::microseconds>(finish1 - start1);
for (int i = 0; i < n; i++)
{
heapMemoryRead[i] = 1;
}
std::memset(heapMemoryWrite, 0, n * sizeof(int));
auto start2 = std::chrono::high_resolution_clock::now();
doWorkSlow(n,heapMemoryWrite, heapMemoryRead, 10);
auto finish2 = std::chrono::high_resolution_clock::now();
auto duration2 = std::chrono::duration_cast<std::chrono::microseconds>(finish2 - start2);
std::cout << "Small inner loop:" << duration1.count() << " microseconds.\n" <<
"Large inner loop:" << duration2.count() << " microseconds." << std::endl;
delete[] heapMemoryWrite;
delete[] heapMemoryRead;
}
查看两个 doWork* 函数,对于每次迭代,我们读取相同的地址添加相同的值并写入相同的地址。我知道在doWorkSlow 实现中,我们正在执行一两个以上的操作来解决j*k + k,但是,我认为相对于为内存读取和执行加载/存储所需的时间而言,假设它是合理安全的。写,这些操作的时间贡献可以忽略不计。
尽管如此,在我的 i7-3700 上使用 g++ --version 7.5.0 时,doWorkSlow 所用的时间(46.8 秒)大约是 doWorkFast(25.5 秒)的两倍。虽然想到了缓存预取和分支预测之类的东西,但我没有很好的解释为什么doWorkFast 比doWorkSlow 快得多。有人有见解吗?
谢谢
【问题讨论】:
-
你的编译标志是什么?
-
我没有使用任何标志,但是当我使用 -o3 -o2 和 -o1 进行编译时,我得到了相似的结果(即慢需要 ~2x)。
-
我的猜测是差异来自数组访问,
doWorkFast是一个接一个地访问数组元素(递增 1),但doWorkSlow将索引递增j+1,这使得缓存预置更加困难。 -
如果不进行优化,您可能会在每次迭代时计算
total/innerLoopSize。j*k+k也是如此。尝试计算一次j*k+k并将其存储在一个临时(常量)变量中,在write语句之前,例如const int index = j * k + k; write[index] = ...;
标签: c++ arrays loops optimization heap-memory