我正在为我的n-body模拟器优化我的代码,在分析我的代码时,已经看到了这个:
这两行,
float diffX = (pNode->CenterOfMassx - pBody->posX);
float diffY = (pNode->CenterOfMassy - pBody->posY);
其中pNode
是指向我已定义的Node
类型对象的指针,并包含(包含其他内容)2个浮点数,CenterOfMassx
和CenterOfMassy
其中pBody
是指向我已定义的类型为Body
的对象的指针,并包含(包含其他内容)2个浮点数,posX
和posY
。< / p>
应该花费相同的时间,但不要。事实上,第一行占功能样本的0.46%,但第二行占5.20%。
现在我可以看到第二行有3条指令,第一条只有一条。
我的问题是为什么这些看似做同样的事情,但在实践中做不同的事情?
答案 0 :(得分:5)
如前所述,探查器仅列出第一行的一条汇编指令,但第二行列出三条汇编指令。但是,因为优化器可以移动很多代码,所以这并不是很有意义。看起来代码已经过优化,可以先将所有值加载到寄存器中,然后执行减法操作。因此它从第一行执行动作,然后从第二行执行动作(加载),接着是第一行的动作和第二行的动作(减法)。由于这很难表示,它只是在显示与代码内联的反汇编时,哪一行最接近哪一行代码。
请注意,第一个加载已执行,并且在执行下一个加载指令时可能仍在CPU管道中。第二个负载与第一个负载中使用的寄存器无关。但是,第一次减法确实如此。该指令要求先前的加载指令在流水线中足够远,结果可以用作减法的操作数之一。当管道允许负载完成时,这可能会导致CPU停顿。
所有这些都强化了内存优化的概念在现代CPU上比CPU优化更重要。例如,如果您之前已将所需的值加载到寄存器15指令中,那么减法可能会更快地发生。
通常,您可以为优化做的最好的事情是保持缓存与您将要使用的内存保持一致,并确保它尽快更新,而不是在需要内存之前。除此之外,优化是复杂的。
当然,所有这一切都因现代CPU更加复杂,可能会提前40-60条out of order execution.指令
为了进一步优化,您可以考虑使用以优化方式执行向量和矩阵运算的库。使用其中一个库,可以使用两个向量指令而不是4个标量指令。
答案 1 :(得分:3)
根据展开的程序集,指令会重新排序,以便在使用pNodes
数据成员进行减法之前加载pBody
的数据成员。目的可能是利用内存缓存。
因此,执行顺序不再与C代码相同。比较第一个C语句中的1个movss和第一个C语句中的1个movss + 2子句是不公平的。
答案 2 :(得分:1)
性能计数器不具有周期精确性。有时错误的指令会受到指责。但在这种情况下,它可能会指向产生其他所有等待结果的指令。
因此,在等待内存访问和FP sub的结果时,它可能会用完它可能做的事情。如果发生缓存未命中,请寻找构建代码以获得更好内存位置的方法,或者至少为了按顺序进行内存访问。硬件预取程序可以检测顺序访问模式,直到步长的某些限制。
此外,您的编译器可以对此进行矢量化。它从顺序地址加载两个标量,然后从顺序地址中减去两个标量。
会更快movq xmm0, [esi+30h] # or movlps, but that wouldn't break the dep
movq xmm1, [edi] # on the old value of xmm0 / xmm1
subps xmm0, xmm1
在diffX
的元素0和1中留下diffY
和xmm0
,而不是2个不同的注册表,因此优势取决于周围的代码。