我有一个紧紧的循环,就像钱德勒·卡鲁斯(Chandler Carruth)在CPP CON 2017上提出的那样: https://www.youtube.com/watch?v=2EWejmkKlxs 在此视频中,在25分钟时出现了这样的循环:
for (int& i:v)
i = i>255?255:i;
其中v
是向量。这与我的程序中使用的代码完全相同,经过分析后,证明需要花费大量时间。
在他的演讲中,钱德勒修改了装配体并加快了循环速度。我的问题是,在实践中,在生产代码中,建议采用什么方法来优化此效果?我们应该在C ++代码中使用内联汇编吗?还是像钱德勒所做的那样,将C ++代码编译到汇编中,然后优化汇编器?
假设采用x86架构,将非常感谢您优化上述for循环的示例。
答案 0 :(得分:2)
我的问题是,在实践中,在生产代码中,建议使用哪种方法来对此进行优化?我们应该在C ++代码中使用内联汇编吗?还是像钱德勒所做的那样,将C ++代码编译到汇编中,然后优化汇编器?
对于生产代码,您需要考虑该软件可以在自动构建系统中进行编译和链接。
在这样的系统中,您如何将代码更改应用于汇编代码?您可能会应用一个diff文件,但是如果更改了优化(或其他)设置,切换到其他编译器或...时,该文件可能会中断。
现在剩下两个选择:将整个函数写入汇编文件(.s)或在C ++代码内包含内联汇编代码-后者可能具有将相关代码保持在同一翻译单元中的优点。
还是让编译器一次生成汇编代码 -并提供最高的优化级别。然后,该代码可以用作您进行手工优化的基础(已经预先优化),然后将其结果作为内联汇编粘贴回C ++源文件或放置到单独的汇编源文件中。
答案 1 :(得分:2)
Chandler修改了编译器的asm输出,因为这是进行一次性实验以找出更改是否有用的简便方法,无需您通常希望将一个asm循环或功能作为项目源代码的一部分。
编译器生成的asm通常是优化循环的一个很好的起点,但是实际上将整个文件保持原样不是将循环的asm实现作为程序的一部分实际维护的好方法,甚至不是可行的方法。参见@Aconcagua的答案。
此外,它无法实现用C ++编写的文件中具有其他任何功能并且可用于链接时优化的目的。
回复:实际夹紧:
请注意,Chandler只是在尝试对非矢量化的代码源进行更改,并禁用了展开+自动矢量化。希望在现实生活中,您可以针对SSE4.1或AVX2,并让编译器使用pminsd
或pminud
自动向量化,以将有符号或无符号int钳位到上限。 (也可以使用其他元素大小。或者不使用SSE4.1,仅使用SSE2,也许可以2x PACKSSDW
=> packuswb
(无符号饱和度),然后用零解包,最多返回4个dword元素向量。 (如果不能仅使用uint8_t[]
的输出!)
顺便说一句,{TW},in the comments of the video, Chandler said证明他犯了一个错误,而他所看到的效果并不是真的由于可预测的分支与cmov所致。可能是代码对齐问题,因为从mov %ebx, (%rdi)
更改为movl $255, (%rdi)
会有所作为!
(不知道AMD CPU是否像P6-系列那样具有寄存器读取停顿功能,应该毫不费力地隐藏将存储耦合到负载的cmov的dep链与通过分支预测+推测打破它分支。)
您很少实际上会想要使用手写循环。通常,只需修改C ++源代码,您就可以手持和/或诱使编译器使asm更符合您的需要。然后,将来的编译器可以自由调整-march=some_future_cpu
的内容。