实际上,如果您仔细编写代码,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
在 add 的进位上,而不是 using adc
,并且仅在来自if you usedadc
的标志结果上使用(add-with-carry)。(或在 32 位目标上)。请参阅gcc 中是否有 128 位整数?- 仅在 64 位目标上,自 GCC4.1 起支持。add
__int128
uint64_t
我用以下代码编译了这段代码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; // test_and_add_carry
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); // test_and_add_carry
}
这是 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
(不确定 push/pop 的ebx
来源,但这仍然不错。)
顺便说一句,所有这些都使用 GCC 4.5.2。