关于循环中值钳位的优化建议

optimisation advice on value clamping in a loop

我有一个紧密的循环,就像 Chandler Carruth 在 CPP CON 2017 中展示的那样: https://www.youtube.com/watch?v=2EWejmkKlxs 在这个视频的第 25 分钟,有一个循环是这样的:

for (int& i:v)
    i = i>255?255:i;

其中 v 是一个向量。这与我的程序中使用的代码完全相同,经过分析后证明它需要花费大量时间。

在他的演讲中,Chandler 修改了程序集并加快了循环速度。我的问题是,实际上,在生产代码中,推荐的优化方法是什么?我们应该在 C++ 代码中使用内联汇编吗?或者像 Chandler 那样,将 C++ 代码编译成汇编然后优化汇编器?

假设 x86 架构,将非常感谢优化上述 for 循环的示例。

My question is, in practice, in a production code, what is the recommended approach to optimise this? Shall we use inline assembly in c++ code? Or like Chandler did, compile C++ code into assembly then optimise the assembler?

对于生产代码,您需要考虑软件可能会在自动构建系统中编译和链接。

您希望如何将代码更改应用于此类系统中的汇编代码?您可能会应用差异文件,但如果更改了优化(或其他)设置、切换到另一个编译器或...

,则可能会中断

现在剩下两个选项:将整个函数写入汇编文件 (.s) 或在 C++ 代码中包含内联汇编代码——后者可能具有将相关代码保留在同一翻译单元中的优势。

我仍然会让编译器生成汇编代码一次 – 具有可用的最高优化级别。然后,此代码可以作为您手动优化的(已经预优化的)基础,然后应将其结果作为内联汇编粘贴回 C++ 源文件或放入单独的汇编源文件中。

Chandler 修改了编译器的 asm 输出,因为这是进行一次性实验以确定更改是否有用的一种简单方法没有 做所有你通常想要包含一个 asm 循环或函数作为项目源代码的一部分的东西。

编译器生成的 asm 通常是优化循环的良好起点,但实际上保持整个文件原样并不是将循环的 asm 实现实际维护为一部分的好方法,甚至不是可行的方法一个程序。请参阅@Aconcagua 的回答。

此外,它破坏了在文件中使用 C++ 编写任何其他函数并可用于 link 时间优化的目的。


回复:实际夹紧:

请注意,Chandler 只是在尝试对非矢量化代码生成进行更改,并禁用展开 + 自动矢量化。在现实生活中,希望您可以以 SSE4.1 或 AVX2 为目标,并让编译器使用 pminsd or pminud for signed or unsigned int clamping to an upper bound. (Also available in other element sizes. Or without SSE4.1, just SSE2, maybe you can 2x PACKSSDW => packuswb(无符号饱和)自动矢量化,然后用零解压缩最多 4 个双字元素向量。 (如果你不能只使用 uint8_t[] 的输出!)

顺便说一句,in the comments of the video, Chandler said 事实证明他犯了一个错误,他看到的效果并不是真正由于可预测的分支与 cmov。这可能是代码对齐问题,因为从 mov %ebx, (%rdi) 更改为 movl 5, (%rdi) 有所不同!

(不知道 AMD CPU 是否像 P6 系列那样有寄存器读取停顿,隐藏 cmov 的 dep 链应该没有问题,将存储耦合到负载 vs. 用分支预测打破它 +猜测过去的一个分支。)


很少会真正想要使用手写循环。通常你可以手握 and/or 欺骗你的编译器使 asm 更像你想要的,只需修改 C++ 源代码。然后未来的编译器可以自由地针对 -march=some_future_cpu.

进行不同的调整