【问题标题】:Square root in assembly, how to shift and change bits汇编中的平方根,如何移位和更改位
【发布时间】:2016-09-29 21:02:26
【问题描述】:

我想在汇编中编写一个快速整数平方根算法,它需要无符号的 32 位。我一直在阅读this,并有了一个想法。这是我的伪代码:

res <- 0
for i from 15 downto 0 do:
   change the ith bit of result to 1
   if res^2 > x then:
      change the ith bit of res back to 0
return res

到目前为止,我已经完成了:

sqrt:
  movl $0, %eax
  movl $15, %edx
  jmp .L8
.L9

.L8
  cmpq cmpq $0, %edx
  jge .L9

我被困在 for 循环操作中,更改第 i 位并进行移位。我也不想使用除法或 sqrt 指令。我知道我可能应该使用shr,但我不知道从哪里开始或如何做。如何在 for 循环中进行操作?我从哪里开始?

【问题讨论】:

  • shr and similar 到底有什么问题?像往常一样设置和清除位:使用 OR 和 AND 掩码。
  • 我的问题是,如果我得到一个整数(例如将 x 设置为 20),我应该如何将其转换为二进制并使用 shift 遍历位?
  • 数字就是数字,二进制只用于想或记下一个数字。你不需要转换任何东西。 Google for AND, OR, SHIFT 你会发现大量的材料。
  • 您可能不会击败硬件双精度平方根指令(Intel Skylake:15-16 周期延迟,吞吐量为每 4-6 个周期一个)。 double 可以精确地表示每个整数,因此唯一的技巧是将 unsigned 整数转换为 double。这在 64 位模式下很容易:只需将零扩展到 64 位并使用CVTSI2SD xmm0, rax,因为每个无符号 32 位整数都适合一个有符号 64 位整数。在 32 位代码中,it takes some work,但还不错。
  • 对于 64 位整数,x87 80 位浮点数可以精确地表示每个 有符号 整数。不过,我认为您可能对无符号 64 位有问题。我认为尾数大小对于每个整数来说太小了,无法以 80 位(长双精度)精确表示。 (还要注意,即使在 32 位模式下,您也可以使用 x87 FILD 与 64 位整数进行转换,并且 x87 FSQRT 并不比 SSE2 SQRTSD 慢多少)。

标签: assembly sqrt


【解决方案1】:

(Intel语法,自行转换成AT&T)

    mov   ebx,<number> ; *number* to find sqrt of
    mov   ecx,0x8000   ; bitmask (starting with b15 bit set)
    ;^^^ 0x8000 = decimal 32768 = binary 1000 0000 0000 0000
    xor   eax,eax      ; result <- 0
sqrt_loop:
    xor   eax,ecx      ; set bit in eax
    push  eax          ; store result (will be destroyed by mul)
    mul   eax          ; edx:eax <- eax*eax (ignoring edx next)
    cmp   eax,ebx      ; compare with *number*
    pop   eax          ; restore result
    jbe   keep_bit     ; res^2 <= *number* -> bit stays set
    xor   eax,ecx      ; unset bit in eax
keep_bit:
    shr   ecx,1        ; next bit
    jnz   sqrt_loop    ; loop till all bits are tried

(我没有尝试+调试它,所以可能有一些错误。但我认为加上你的伪算法和你对 AT&T 的调试重写应该足以让你开始)

正如玛格丽特所指出的,数字就是数字,它就是价值。因此,0x8000 已经在 CPU 线路中编码为 b15 设置为 1,其他位设置为 0。当您要将值从/转换为字符串时,所有转换工作都会发生,但只要您使用值进行计算,它就在同时登记所有形式的登记册。这仅取决于您如何看待寄存器。在源代码中使用 hexa/decimal/binary 就是,编写数字的 STRING 表示形式,由汇编程序将其转换为值本身。

二进制表示是特殊的,因为 CPU 可以处理特定位(使用和/异或/或、旋转、位测试/设置等),因为它以“线”的形式具有这些值,并且它是用于它。就像人类在计算“10*3456”时“作弊”一样,在最后只写额外的 0 以获得结果,因为在十进制格式中 10* 是特殊的。对于 CPU,位操作和所有类型的 2 数学运算也会发生同样的情况。但小数技巧是不可能的,那些有 CPU 以正确的方式计算,乘以 10 为实数。

无论如何,当您只有位号,并且您想获取位掩码本身时,例如如何从 15 获取 0x8000:

mov   ecx,15  ; i-th bit
mov   eax,1   ; set b0 (lowest bit)
shl   eax,cl  ; shift all bits (all zeroed + b0 set) cl-many times left
; eax now contains 0x8000 = b15 set, other bits zeroed

因此,如果您坚持自己的算法方式,则每次都必须重新计算针对位掩码的 for 计数器(或使用一些我不知道的位设置/重置指令,因为几乎不需要他们)。

但是,如果您研究我的代码,您会发现有直接的快捷方式可以处理位掩码本身,而无需计算“第 i 位”部分,从而使代码更短更快(尽管我可能被那个推送 /弹出,也许使用像esi 这样的更多寄存器来存储值会更好......然后这再次演示了如何使用堆栈,以及标志如何不受某些指令的影响,因此您可以使用cmp如果您小心不要修改所需的标志,则会导致延迟方式)。

【讨论】:

  • 另外值得一提的是:btsbtr 是设置/清除寄存器第 i 位的最快方法。 (他们还从以前的位设置了 CF,但这不是问题)。此外,内循环内的 push/pop 非常愚蠢。您可以将单个 MOV 用于额外的寄存器,因为您还远远没有用完。 (当你不想要上半部分时,你应该使用imul r32, r32,这样你就不必破坏EDX。它比mul r32更快,因为它只需要产生一个寄存器输出。)
  • 这是一个很好的答案,非常感谢您抽出宝贵的时间。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-13
  • 1970-01-01
  • 2021-04-10
  • 1970-01-01
  • 1970-01-01
  • 2014-09-08
  • 2017-04-15
相关资源
最近更新 更多