我在内存中有两个128位的十六进制数字,例如(小端):
x:0x12 0x45 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
y:0x36 0xa1 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
我要在这两个数字之间执行无符号乘法,所以我的新数字将是:
z:0xcc 0xe3 0x7e 0x2b 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
现在,我知道我可以将半个x和y数字移动到rax
和rbx
寄存器中,例如,执行mul
操作,并执行相同操作与另一半。问题是,通过这样做,我失去了结转,我不知道如何避免这种情况。大约4个小时我遇到了这个问题,我能看到的唯一解决方案是二进制转换(and
< - > shl,1
)。
你能就这个问题给我一些意见吗? 我认为最好的解决方案是占用一个字节的时间。
答案 0 :(得分:8)
设μ= 2 64 ,然后我们可以将您的128位数 a 和 b 分解为 a = a 1 μ+ a 2 和 b = b < / em> 1 μ+ b 2 。然后我们可以通过首先计算部分乘积来计算 c = ab ,64·64→128位乘法:
q 1 μ+ q 2 = a 2 子> b'/ EM> <子> 2 子>
r 1 μ+ r 2 = a 1 b'/ EM> <子> 2 子>
s 1 μ+ s 2 = a 2 b'/ EM> <子> 1 子>
t 1 μ+ t 2 = a 1 b'/ EM> <子> 1 子>
然后将它们累加到256位结果中(在添加时观察溢出!):
c = t 1 μ 3 +( t 2 + s 1 + r 1 )μ 2 + ( s 2 + r 2 + q 1 )μ+ q 2
答案 1 :(得分:6)
像往常一样,询问编译器如何有效地执行某些操作:64位平台上的GNU C支持__int128_t
和__uint128_t
。
__uint128_t mul128(__uint128_t a, __uint128_t b) { return a*b; }
imul rsi, rdx # tmp94, b
mov rax, rdi # tmp93, a
imul rcx, rdi # tmp95, a
mul rdx # b
add rcx, rsi # tmp96, tmp94
add rdx, rcx #, tmp96
ret
由于这是针对x86-64 System V调用约定,a
位于RSI:RDI中,而b
位于RCX:RDX中。 结果以RDX:RAX 返回。
非常漂亮,只需要一条MOV指令,因为gcc不需要a_upper * b_lower的高半结果,反之亦然。它可以用更快的2操作数形式的IMUL来破坏输入的高半部分,因为它们只使用一次。
使用-march=haswell
启用BMI2,gcc使用MULX来避免一个MOV。
有时编译器输出并不完美,但通常一般策略是手动优化的良好起点。
当然,如果你真正想要的东西在C中是128位乘法,那么只需使用编译器内置的支持。这可以让优化器完成它的工作,通常会提供比你在inline-asm中编写几个部分更好的结果。 (https://gcc.gnu.org/wiki/DontUseInlineAsm)。