【问题标题】:function Power(x,y) in assembly ia32 max 32 bit汇编 ia32 max 32 位中的函数 Power(x,y)
【发布时间】:2018-11-16 16:11:51
【问题描述】:

我使用 IA32 程序集,

我想创建一个函数,给定两个输入数字,以幂的值为基础,结果必须是 32 位中的最大值。基数是无符号整数,基数总是正数,而指数是整数。所以都是负数和 0。在此先感谢

【问题讨论】:

  • 您好 giggi,您能否提供到目前为止您编写的不起作用的代码,以及所需两个输入和您寻求的确切输出的测试用例?这里习惯于根据最少的可重复示例提供非常具体的问题。谢谢,祝你好运:)
  • 哪些值范围对性能很重要?你能假设像cmov 这样的 PPro 指令在移位循环内对 0/1 检查进行无分支处理吗?这对于吞吐量来说可能是值得的,即使它会产生潜在的长数据依赖关系。您将需要使用通常的整数幂算法。 wiki.c2.com/?IntegerPowerAlgorithm
  • 相关:Why isn't int pow(int base, int exponent) in the standard C++ libraries? 对线性 O(n) 时间与 O(log2(n)) 时间移位实现进行了一些性能实验,其中编译器希望将其优化为循环, 与使用浮点的标准库函数相比,对于非常小的n 以外的情况,它实际上可以更快。见How can I write a power function myself?
  • 对于 FP 版本,您需要 x87 FYL2X 等,或者 exp2 和 log2 的 SSE 实现。请参阅Fastest Implementation of Exponential Function Using AVX 和Efficient implementation of log2(__m256d) in AVX2,了解有关制作快速版本的一些技巧,该版本可能足够准确,可以在转换回整数时四舍五入到正确的最接近整数。

标签: function assembly x86


【解决方案1】:

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

对于整数返回值,只需检查正数n 或返回1 或0。对于 FP 返回值,您可以使用绝对值的无符号实现,并有条件地取倒数。

对于较大的n 幅度,您可能希望使用基于 FP exp/log 的实现(请参阅我的 cmets 和 How can I write a power function myself?),而不是循环位实现。


对于具有无符号指数(或有符号正数)的纯整数,使用通常的指数右移算法并将结果相乘,可以实现一个不错的无分支实现 如果当前位已设置。 (请参阅https://eli.thegreenplace.net/2009/03/21/efficient-integer-exponentiation-algorithms 了解算法背后的数学原理和 Python 代码。)

我们可以使用shr 右移和 CMOV 对移出的位,并循环分支剩余的值。

这个版本在 x86-64 System V 相同的寄存器中传递 args,但它可以在 32 位模式下进行汇编。您当然可以将其调整为您喜欢的任何调用约定;它需要 4 个寄存器,因此您可能需要在 32 位调用约定中保存/恢复调用保留的 reg。

它与直接移植 Python 实现的 x86-64 C 编译器类似,但比它更好。 (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

在 Broadwell 或更高版本的 Intel CPU 或 AMD Ryzen 上,我们有 1 个周期 CMOV 和 3 个周期延迟imul,这有望在每次迭代中运行 4 个周期(imul -> 通过 EAX 的 cmov 依赖链)。

imul 在现代 x86 上完全流水线化(或至少在 AMD Bulldozer 系列上充分流水线化),但每个时钟的吞吐量仅为 1,因此两个 imul 指令之间存在潜在的资源冲突,可能都在等待edi 准备好。但是通过 EDI 的 3 周期 dep 链应该领先于 4 周期 imul/cmov 链,因此在 imul eax,edi 和 imul edi,edi 都准备好开始的任何周期中,最旧的就绪优先调度应该使正确的选择并启动imul eax,edi。

注意mov edx,eax 不在关键路径:它可以与imul 并行运行。如果我完成了tmp *= edi,mov 将处于关键路径上,并且会在不消除整数寄存器的情况下影响 CPU 的延迟。


当然,max loop trip-count只有32(如果设置了指数的高位),所以乱序执行可以看穿这个直到循环结束(希望能解决循环-在乘法到达那里之前退出错误预测)。

循环中的指令很少(与其吞吐量相比),因此它应该能够与之前/之后的独立指令显着重叠。

预期延迟约为4 cycles *trip_count = 4 * log2(n),即4 * 指数中最高设置位的位置。


对于这个的 FP 版本,x87 实际上可能对fcmov 很感兴趣。否则,您可能会使用移位和 SSE4 blendvps 根据另一个寄存器的高位进行混合。 0.0 是加法恒等式,而不是乘法恒等式,因此与比较结果进行“与”运算不仅有效。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-11
    • 2010-11-24
    • 2014-09-30
    • 2016-06-28
    • 1970-01-01
    • 2016-06-28
    • 2015-01-08
    • 2011-03-08
    相关资源
    最近更新 更多