我刚刚发现间接成本大约是浮点乘法的 3 倍!
这是可以预料的吗?我的测试错了吗?
背景
读完How much does pointer indirection affect efficiency? ,我对间接成本感到 panic 。
Going through a pointer indirection can be much slower because of how a modern CPU works.
在我过早地优化我的真实代码之前,我想确保它真的像我担心的那样花费很多。
我做了一些技巧来找到粗略的数字 (3x),如下所示:-
第一步
- Test1:没有间接 -> 计算一些东西
- Test2:间接 -> 计算一些东西(相同)
我发现 Test2 比 Test1 花费更多时间。
这里没有什么奇怪的。
第二步
- Test1:没有间接 -> 计算一些昂贵的东西
- Test2:间接 -> 计算便宜的东西
我尝试更改我在calculate something expensive
中的代码,使其逐渐变得更昂贵,以使两个测试 的成本接近相同。
结果
最后,我发现使两个测试使用相同时间量(即收支平衡)的可能函数之一是:-
- Test1:无间接 -> 返回
float*float*...
3 次 - Test2 :间接 -> 简单地返回一个
float
这是我的测试用例(ideone demo):-
class C{
public: float hello;
public: float hello2s[10];
public: C(){
hello=((double) rand() / (RAND_MAX))*10;
for(int n=0;n<10;n++){
hello2s[n]= ((double) rand() / (RAND_MAX))*10;
}
}
public: float calculateCheap(){
return hello;
}
public: float calculateExpensive(){
float result=1;
result=hello2s[0]*hello2s[1]*hello2s[2]*hello2s[3]*hello2s[4];
return result;
}
};
这是主要的:-
int main(){
const int numTest=10000;
C d[numTest];
C* e[numTest];
for(int n=0;n<numTest;n++){
d[n]=C();
e[n]=new C();
}
float accu=0;
auto t1= std::chrono::system_clock::now();
for(int n=0;n<numTest;n++){
accu+=d[n].calculateExpensive(); //direct call
}
auto t2= std::chrono::system_clock::now();
for(int n=0;n<numTest;n++){
accu+=e[n]->calculateCheap(); //indirect call
}
auto t3= std::chrono::system_clock::now();
std::cout<<"direct call time ="<<(t2-t1).count()<<std::endl;
std::cout<<"indirect call time ="<<(t3-t2).count()<<std::endl;
std::cout<<"print to disable compiler cheat="<<accu<<std::endl;
}
直接调用时间和间接调用时间调整为与上面提到的相似(通过编辑calculateExpensive
)。
结论
间接成本 = 3x 浮点乘法。
在我的桌面(带有 -O2 的 Visual Studio 2015)中,它是 7 倍。
问题
是否可以预期间接成本约为浮点乘法的 3 倍?
如果不是,我的测试怎么错了?
(感谢enhzflep建议改进,已编辑。)
最佳答案
简而言之,您的测试非常不具有代表性,实际上并没有准确衡量您可能认为的结果。
请注意,您调用了 new C()
100'000 次。这将在你的内存中创建 100'000 个 C 实例,每个实例都非常小。如果您的内存访问是有规律的,现代硬件非常擅长预测。由于每次分配、每次调用 new 都是独立发生的,因此内存地址不会很好地组合在一起,从而使这种预测变得更加困难。这会导致所谓的缓存未命中。
分配为数组 (new C[numTest]
) 可能会产生完全不同的结果,因为在这种情况下地址再次非常可预测。尽可能紧密地将内存分组并以线性、可预测的方式访问它通常会提供更好的性能。这是因为大多数缓存和地址预取器都希望在普通程序中出现这种模式。
次要添加:像这样初始化 C d[numTest] = {};
将在每个元素上调用构造函数
关于c++ - 间接成本 ~ 浮点乘法的 3 倍,真的吗? (带演示),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44171675/