程序集ia32中的Power(x,y)函数最大32位

时间:2018-11-16 16:11:51

标签: function assembly x86

i使用IA32程序集

我想创建一个函数,给定两个输入数字,该函数将保留幂的值,结果必须以32位为最大值。指数为int时,基数始终为正,且为unsigned int。所以都是负数和0。预先感谢

1 个答案:

答案 0 :(得分:1)

结果也应该是整数吗? x^-n只是1/x^n,对于x以外的任何1舍入为零。例如pow(16, -2)1/256

对于整数返回值,只需检查正n或返回10。对于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链,因此在ediimul 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是加性身份,而不是乘法身份,因此与比较结果进行“与”操作不仅无效。