i使用IA32程序集
我想创建一个函数,给定两个输入数字,该函数将保留幂的值,结果必须以32位为最大值。指数为int时,基数始终为正,且为unsigned int。所以都是负数和0。预先感谢
答案 0 :(得分:1)
结果也应该是整数吗? x^-n
只是1/x^n
,对于x
以外的任何1
舍入为零。例如pow(16, -2)
是1/256
。
对于整数返回值,只需检查正n
或返回1
或0
。对于FP返回值,可以使用具有绝对值的无符号实现,并有条件地取倒数。
对于大的n
,您可能希望使用基于FP exp / log的实现(请参阅我对问题的评论和How can I write a power function myself?),而不是逐位循环实现
对于具有无符号指数(或有符号正数)的纯整数,可以使用通常的右移指数并将结果相乘的算法,实现良好的无分支实现 如果当前位已设置。 (有关算法背后的数学知识和Python代码,请参见https://eli.thegreenplace.net/2009/03/21/efficient-integer-exponentiation-algorithms。
我们可以使用shr
右移,对移出的位进行CMOV,然后对剩余值进行循环分支。
此版本在x86-64 System V所用的同一寄存器中传递args,但它将以32位模式进行汇编就可以了。当然,您可以使其适应于您喜欢的任何调用约定。它需要4个寄存器,因此您可能需要按32位调用约定保存/恢复保留了呼叫的reg。
它与x86-64 C编译器提供的Python实现直接端口相似但更好。 (https://godbolt.org/z/L9Kb98 gcc / clang在其中包含test sil,1
/ cmov`的循环结构,与shr结果上的循环分支分开。)
;; untested
; integer power
; args in EDI,ESI (like the x86-64 System V calling convention)
; returns in EAX
; clobbers: EDX, EDI, ESI
ipown: ; (int a (EDI), unsigned n (ESI))
mov eax, 1 ; res = 1
test edi,edi
jz .zero_exponent
.loop:
mov edx, eax ; tmp = res
imul eax, edi ; res *= a (will be overwritten with old res if n&1 == 0)
imul edi, edi ; a*=a
shr esi, 1 ; n>>=1 setting ZF according to result, and CF= bit shifted out (old_n&1)
cmovnc eax, edx ; res = tmp if the bit was zero so we don't do res *= a this time
jnz .loop
.zero_exponent:
ret
在我们拥有1个周期CMOV和3个周期延迟imul
的Broadwell或更高版本的Intel CPU或AMD Ryzen上,希望每个迭代以4个周期运行(imul->通过EAX的cmov依赖链)
imul
已在现代x86上完全流水化(或至少在AMD Bulldozer系列上已充分流水线化),但是每个时钟的吞吐量仅为1,因此这两个{{1}之间存在潜在的资源冲突}可能都在等待imul
准备就绪的指令。但是通过EDI的3周期dep链应该领先于4周期imul / cmov链,因此在edi
和imul eax,edi
都准备开始的任何周期上,最早的-第一次调度应该做出正确的选择,然后开始imul edi,edi
。
请注意,imul eax,edi
偏离了关键路径:它可以与mov edx,eax
并行运行。如果我完成了imul
,那么tmp *= edi
将会处于关键路径上,并且会在不移动整数寄存器的情况下损害CPU的延迟。
当然,最大循环跳闸计数仅为32(如果指数中设置了高位),因此乱序执行可以看到此现象直至循环结束(并希望解决该循环问题) -在乘数到达之前误测出结果。
此循环中的指令很少(与其吞吐量相比),因此它应该能够与之前/之后的独立指令显着重叠。
期望的等待时间大约为mov
= 4 cycles *trip_count
,即4 *指数中最高设置位的位置。
对于此版本的FP版本,对于4 * log2(n)
,x87实际上可能很有趣。否则,您可能会使用移位和SSE4 fcmov
来根据另一个寄存器的高位进行混合。 blendvps
是加性身份,而不是乘法身份,因此与比较结果进行“与”操作不仅无效。