如果你仔细编写代码,gcc实际上会自动使用进位。
当前GCC可以将hiWord += (loWord < loAdd)
优化为add
/adc
(x86的add-with-carry)。这个优化是在GCC5.3中引入的。
(编辑注:当然,难点在于编写一个带进位和进位输出的正确的全加器;在C中这很困难,而且我没有看到GCC如何优化任何此类操作。)
另外参考:https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html 可以从无符号或有符号溢出检测中获得进位输出。
旧版本的GCC(如GCC4.5)将在加法的进位输出上分支或setc
,而不是使用adc
,并且只有在使用__int128
时才会在add
的标志结果上使用adc
(带进位相加)。 (或在32位目标上使用uint64_t
)。请参见GCC中是否有128位整数? - 仅适用于64位目标,自GCC4.1以来受到支持。
我使用gcc -O2 -Wall -Werror -S
编译了这段代码:
void increment128_1(unsigned long &hiWord, unsigned long &loWord)
{
const unsigned long hiAdd=0x0000062DE49B5241;
const unsigned long loAdd=0x85DC198BCDD714BA;
loWord += loAdd;
if (loWord < loAdd) ++hiWord;
hiWord += hiAdd;
}
void increment128_2(unsigned long &hiWord, unsigned long &loWord)
{
const unsigned long hiAdd=0x0000062DE49B5241;
const unsigned long loAdd=0x85DC198BCDD714BA;
loWord += loAdd;
hiWord += hiAdd;
hiWord += (loWord < loAdd);
}
这是用于increment128_1的汇编代码:
.cfi_startproc
movabsq $-8801131483544218438, %rax
addq (%rsi), %rax
movabsq $-8801131483544218439, %rdx
cmpq %rdx, %rax
movq %rax, (%rsi)
ja .L5
movq (%rdi), %rax
addq $1, %rax
.L3:
movabsq $6794178679361, %rdx
addq %rdx, %rax
movq %rax, (%rdi)
ret
...这是increment128_2的汇编代码:
movabsq $-8801131483544218438, %rax
addq %rax, (%rsi)
movabsq $6794178679361, %rax
addq (%rdi), %rax
movabsq $-8801131483544218439, %rdx
movq %rax, (%rdi)
cmpq %rdx, (%rsi)
setbe %dl
movzbl %dl, %edx
leaq (%rdx,%rax), %rax
movq %rax, (%rdi)
ret
请注意第二个版本中缺少条件分支。
[编辑]
此外,引用通常对性能不利,因为GCC必须担心别名... 直接按值传递事物通常更好。考虑:
struct my_uint128_t {
unsigned long hi;
unsigned long lo;
};
my_uint128_t increment128_3(my_uint128_t x)
{
const unsigned long hiAdd=0x0000062DE49B5241;
const unsigned long loAdd=0x85DC198BCDD714BA;
x.lo += loAdd;
x.hi += hiAdd + (x.lo < loAdd);
return x;
}
汇编语言:
.cfi_startproc
movabsq $-8801131483544218438, %rdx
movabsq $-8801131483544218439, %rax
movabsq $6794178679362, %rcx
addq %rsi, %rdx
cmpq %rdx, %rax
sbbq %rax, %rax
addq %rcx, %rax
addq %rdi, %rax
ret
这实际上是这三个代码中最紧凑的。
...好吧,事实上它们都没有自动使用进位 :-). 但它们确实避免了条件分支,我打赌这是慢的部分(因为分支预测逻辑会一半的时间预测错误)。
[编辑2]
还有一个,我在搜索时偶然发现。你知道GCC内置支持128位整数吗?
typedef unsigned long my_uint128_t __attribute__ ((mode(TI)));
my_uint128_t increment128_4(my_uint128_t x)
{
const my_uint128_t hiAdd=0x0000062DE49B5241;
const unsigned long loAdd=0x85DC198BCDD714BA;
return x + (hiAdd << 64) + loAdd;
}
这个程序的汇编语言可以说是最好的:
.cfi_startproc
movabsq $-8801131483544218438, %rax
movabsq $6794178679361, %rdx
pushq %rbx
.cfi_def_cfa_offset 16
addq %rdi, %rax
adcq %rsi, %rdx
popq %rbx
.cfi_offset 3, -16
.cfi_def_cfa_offset 8
ret
(不确定 ebx
的推入/弹出是从哪里来的,但这还不错。)
顺便说一下,所有这些都是使用 GCC 4.5.2 进行的。