我刚刚发现,间接成本大约是浮动乘法的3倍!
这是我们所期望的吗?我的测试错了吗?
背景
在我读了指针间接对效率的影响有多大?之后,我对间接成本感到恐慌。
通过指针间接可能要慢得多,因为现代CPU的工作方式。
在我过早地优化我真正的代码之前,我希望确保它真的像我所担心的那样花费很多。
我用一些技巧来找出粗数(3x),如下所示:-
第一步
我发现Test2比Test1花费更多的时间。
这里没什么好惊讶的。
第二步
我试图修改calculate something expensive中的代码,使其变得越来越昂贵,从而使两个测试的成本几乎相同。
结果
最后,我发现使两个测试使用相同时间(即盈亏平衡)的一个可能功能是:-
float*float*... 3次float这是我的测试用例(ideone演示) :-
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;
}直接呼叫时间‘s和间接呼叫时间被调成类似于上面提到的(通过编辑calculateExpensive)。
结论
间接成本= 3x浮点乘法。
在我的桌面(使用-O2的Visual 2015 )中,它是7x。
问题
这是预期的间接成本约3倍的浮点乘法?
如果没有,我的测试怎么会出错?
(感谢enhzflep建议改进,它是编辑的。)
发布于 2017-05-26 12:29:11
间接的成本主要由Cache错过。因为老实说,Cache错过比你说的任何东西都要昂贵得多,其他的一切最终都是舍入错误。
缓存丢失和间接性可能比您的测试显示的要昂贵得多。
这主要是因为您只有10万个元素,而CPU缓存可以缓存其中的每一个浮点。顺序堆分配将倾向于集群化。
你会得到一堆缓存错过,但不是每个元素一个。
你们两个案子都是间接的。“间接”案例必须遵循两个指针,而“直接”案例必须执行指针算法的一个实例。“昂贵”的情况可能适用于某些SIMD,特别是如果您有轻松的浮点精度(允许乘法重排序)。
正如所见的这里,或这幅图像 (不是内联的,我没有权限),主内存引用的数量将主导上述代码中的几乎任何其他内容。2 Ghz CPU的周期时间为0.5 ns,主内存基准值为100 ns或200次延迟周期。
同时,如果您能够执行矢量化代码,桌面CPU可以在每个周期中执行8+浮点操作。这是一个潜在的1600倍的浮点操作比一个缓存错过。
间接可能会花费您能够使用矢量化指令(8倍减速),如果缓存中的所有内容仍然需要L2缓存引用(14倍的减速),而不是其他选择。但是,与200 ns的主内存参考延迟相比,这些减速是很小的。
请注意,并不是所有CPU都具有相同的向量化水平,正在努力加快CPU/主内存延迟,FPU具有不同的特性,以及各种各样的其他复杂问题。
发布于 2017-05-25 04:23:40
简单地说,你的测试是非常没有代表性的,而且实际上并不能准确地衡量你认为它所做的事情。
请注意,您调用new C() 100'000次。这将创建100,000个C实例,分散在您的内存中,每个实例都非常小。现代硬件非常擅长预测内存访问是否正常。因为每一次分配,每一次对new的调用都是独立于其他调用的,因此内存地址将不能很好地组合在一起,从而使这种预测变得更加困难。这导致所谓的缓存丢失。
分配为数组(new C[numTest])可能会产生完全不同的结果,因为在这种情况下,地址是非常可预测的。将内存尽可能紧密地组合在一起,并以线性、可预测的方式访问它,通常会提供更好的性能。这是因为大多数缓存和地址预取器都期望这种模式发生在公共程序中。
小的添加:像这样初始化C d[numTest] = {};将调用每个元素的构造函数
发布于 2017-05-25 04:03:47
你的问题没有简单的答案。它取决于硬件的功能和特性(CPU、RAM、总线速度等)。
回到过去,浮点乘数可能需要几十个甚至几百个周期。内存访问的速度与CPU频率相似(请考虑这里的MegaHertz ),浮点乘法所需的时间比间接的要长。
从那以后,情况发生了很大变化。现代硬件可以在一两个周期内执行浮点乘法,而间接(内存访问)可能只需几个周期到数百个周期,这取决于要读取的数据位于何处。可以有几个级别的缓存。在极端情况下,通过间接访问的内存已被交换到磁盘,需要重新读取。这将有数千个周期的延迟。
通常,为浮点乘法提取操作数和解码指令所需的开销可能比实际乘法要长。
https://stackoverflow.com/questions/44171675
复制相似问题