我有以下的代码片段:
static long F(long a, long b, long c, long d)
{
return a + b + c + d;
}
生成:
<Program>$.<<Main>$>g__F|0_0(Int64, Int64, Int64, Int64)
L0000: add rdx, rcx
L0003: lea rax, [rdx+r8]
L0007: add rax, r9
L000a: ret
如果我从这篇《乱序执行》手册(§ Out of order execution)(来源)中正确理解的话,上面的代码可以转化为
((a + b) + c) + d
。为了计算这个式子,CPU必须等待第一个括号和第二个括号等等。在这里我们可以看到LEA位于中间,这意味着它们无法并行执行(如果我理解正确的话)。因此,作者建议:
将“独立”的括号配对:
static long G(long a, long b, long c, long d)
{
return (a + b) + (c + d);
}
但是这会生成相同的汇编代码:
<Program>$.<<Main>$>g__G|0_1(Int64, Int64, Int64, Int64)
L0000: add rdx, rcx
L0003: lea rax, [rdx+r8]
L0007: add rax, r9
L000a: ret
相比之下,这是
C
代码使用GCC(O2)
生成的结果:int64_t
f(int64_t a, int64_t b, int64_t c, int64_t d) {
return a + b + c + d;
}
int64_t
g(int64_t a, int64_t b, int64_t c, int64_t d) {
return (a + b) + (c + d);
}
以下是输出结果:
f:
add rcx, rdx ; I guess -O2 did the job for me.
add rcx, r8 ; I guess -O2 did the job for me.
lea rax, [rcx+r9]
ret
g:
add rcx, rdx
add r8, r9
lea rax, [rcx+r8]
ret
问题
- 我是否正确理解了手册?这两个
ADD
应该连在一起(中间没有LEA
)吗?如果是,我如何提示C#
编译器不要忽略我的括号?
lea add add
,add lea add
和add add lea
,这并没有什么区别,它们之间的差异在于它们的依赖图(允许前两个指令并行执行或不允许)。 - haroldint64_t
上有愚蠢的优化错误——C中有符号溢出是未定义行为,似乎阻止了它将其视为可结合的。使用uint64_t
可以使其正确地进行双向优化:先进行两个独立的加法,然后组合成对。https://godbolt.org/z/W13WEjjMh。Clang无论如何都会自我打败,像C#的JIT一样串行化操作,即使你在源代码中提示它使用更多的指令级并行性。 - Peter Cordes