【问题标题】:x86 assembly abs() implementation?x86 程序集 abs() 实现?
【发布时间】:2011-02-08 00:16:30
【问题描述】:

我需要得到 2 个有符号整数的差值。 x86 汇编语言中是否有 ABS() 函数,所以我可以这样做。任何帮助将不胜感激。

【问题讨论】:

  • 你可以比较和有条件地交换,然后减去。
  • 你在哪个平台上?没有“汇编语言”之类的东西,只有“x86 汇编”或“ARM 汇编”等。
  • 如何比较和有条件地交换,然后减去。你能提供一个x86的例子吗..
  • 你的意思是距离,而不是差异
  • 是否可以将两个整数的符号位都设置为0?

标签: assembly x86


【解决方案1】:

这就是 C 库函数 abs() 在汇编中不分支的方式:

   abs(x) = (x XOR y) - y

其中y = x >>> 31(假设输入为 32 位),>>> 是算术右移运算符。

上式解释: 我们只想生成负 x 的 2 补码。

y = 0xFFFF, if x is negative
    0x0000, if x is positive

所以当 x 为正时 x XOR 0x0000 等于 x 。当x 为负时,x XOR 0xFFFF 等于x 的补码。现在我们只需要添加 1 来得到它的 2 的补码,这就是表达式 -y 正在做的事情。因为0xFFFF 在十进制中是 -1。

让我们看看gcc(我的机器上是4.6.3)为以下代码生成的程序集:

C 代码:

main()
{
  int x;
  int output = abs(x);
}

gcc 4.6.3 使用我的 cmets 生成程序集 sn-p(AT&T 语法):

  movl  -8(%rbp), %eax    # -8(%rbp) is memory for x on stack
  sarl  $31, %eax         #  shift arithmetic right: x >>> 31, eax now represents y
  movl  %eax, %edx        #  
  xorl  -8(%rbp), %edx    #  %edx = x XOR y
  movl  %edx, -4(%rbp)    # -4(%rbp) is memory for output on stack
  subl  %eax, -4(%rbp)    # (x XOR y) - y

BONUS(来自Hacker's Delight):如果您有一个快速乘以 +1 和 -1,以下将为您提供 abs(x)

      ((x >>> 30) | 1) * x

【讨论】:

  • 之所以有效,是因为本例中的y 基本上是输入的符号(0=正数或零,111....111=负数)。而xor 为零不会改变数字。
【解决方案2】:

旧线程,但如果我在这里冲浪很晚,你可能也会... abs 是一个很好的例子,所以应该在这里。

; abs(eax), with no branches.
; intel syntax (dest, src)

mov ebx, eax ;store eax in ebx
neg eax
cmovl eax, ebx ;if eax is now negative, restore its saved value

【讨论】:

  • 通过避免branch predictor,这非常简单高效,绝对应该被接受为答案。
【解决方案3】:

如果是 x86 程序集,下面的 according to the ever useful wikipedia 应该可以工作。从另一个值中减去一个值,然后对结果使用以下说明:

cdq
xor eax, edx
sub eax, edx

【讨论】:

    【解决方案4】:

    如果你想正确处理所有情况,你不能只减去然后取绝对值。你会遇到麻烦,因为两个有符号整数的差不一定可以表示为有符号整数。例如,假设您使用 32 位 2s 补码整数,并且您想找出 INT_MAX (0x7fffffff) 和 INT_MIN (0x80000000) 之间的差异。减法:

    0x7fffffff - 0x80000000 = 0xffffffff
    

    这是-1;当你取绝对值时,你得到的结果是1,而这两个数字之间的实际差是0xffffffff,被解释为一个无符号整数(UINT_MAX)。

    两个有符号整数之间的差异总是可以表示为一个无符号整数。要获得这个值(使用 2s 补码硬件),您只需从较大的输入中减去较小的输入,并将结果解释为无符号整数;不需要绝对值。

    这是在 x86 上执行此操作的一种(许多但不一定是最好的)方法,假设两个整数在 eaxedx 中:

        cmp   eax,  edx  // compare the two numbers
        jge   1f
        xchg  eax,  edx  // if eax < edx, swap them so the bigger number is in eax
    1:  sub   eax,  edx  // subtract to get the difference
    

    【讨论】:

    • 使用jge 可能会导致cpu 中的branch predictor 变为mis-prediction,这会大大降低cpu 的速度。因此,如果关注性能,最好使用@bits 或@Hal 的答案
    【解决方案5】:

    假设您的整数在 MMX 或 XMM 寄存器中,请使用 psubd 计算差值,然后使用 pabsd 获取差值的绝对值。

    如果您的整数在普通的“普通”寄存器中,则进行减法运算,然后使用 cdq 技巧获得绝对值。这需要使用一些特定的寄存器(cdq 符号将eax 扩展为edx,不使用其他寄存器),因此您可能想要使用其他操作码进行操作。例如:

    mov  r2, r1
    sar  r2, 31
    

    在寄存器r2 中计算r1 的符号扩展(如果r1 为正或为零,则为0,如果r1 为负,则为0xFFFFFFFF)。这适用于所有 32 位寄存器 r1r2 并替换 cdq 指令。

    【讨论】:

      【解决方案6】:

      一个简短但直接的方法,使用条件移动指令(我认为可用 Pentium 及更高版本):

      ; compute ABS(r1-r2) in eax, overwrites r2
      mov eax, r1
      sub eax, r2
      sub r2, r1
      cmovg eax, r2
      

      sub 指令设置标志与 cmp 指令相同。

      【讨论】:

      • cmov 是 P6 (ppro/PII) 的新功能,但是现在你可以假设它。 gcc 可以。
      【解决方案7】:

      ABS(EAX)

        test   eax, eax   ;  Triger EFLAGS [CF, OF, PF, SF, and ZF]
        jns AbsResult     ;  If (SF) is off, jmp AbsResult
        neg    eax        ;  If (SF) is on. (negation nullify by this opcode)
      AbsResult:
      

      如果标志已经由在 eax 中生成的值设置,则不需要test。如果输入值在正负之间随机分布,则分支错误预测会使其变慢。

      这对 RAX、AX、AL 的工作方式相同。

      【讨论】:

      • or reg, reg 总是比test reg,reg 更糟糕的选择。 stackoverflow.com/questions/33721204/…。此外,分支不是“一个时钟”。它们要么是~0(预测正确),要么是~15个时钟(预测错误)。
      【解决方案8】:

      有 SUB 指令,如果你想做的是做 A-B。 高温

      【讨论】:

        猜你喜欢
        • 2011-11-10
        • 2013-08-16
        • 2014-08-31
        • 1970-01-01
        • 1970-01-01
        • 2012-03-11
        • 2011-05-26
        • 2014-08-07
        • 1970-01-01
        相关资源
        最近更新 更多