如何在x86中计算2的幂的和?

时间:2019-06-08 20:33:13

标签: assembly x86 masm

我想计算一个以2为底的指数序列。例如,如果给定3,则程序将计算(2 ^ 3 + 2 ^ 2 + 2 ^ 1 + 2 ^ 0)。问题是我无法计算与之相乘的指数。

我尝试了shl来计算指数,并且尝试了循环。均未产生正确的结果,下面是我的最新尝试。

.586
.MODEL FLAT
INCLUDE io.h                            
.STACK 4096

.DATA

        Exponent    DWORD   ?           
        Prompt  BYTE    "Enter an exponent", 0

        Base        DWORD  2

        string      BYTE    40 DUP (?)

        resultLbl   BYTE    "The solution is", 0
        Solution    DWORD   20 DUP (?), 0

.CODE
_MainProc PROC

    input   Prompt, string, 40      ; read ASCII characters (N)
        atod    string              ; ascii to double
        mov     exponent, eax       ; store in memory     


        push Exponent
        push Base

        call function
        add esp, 8

        dtoa   Solution, eax         ; convert to ASCII characters
        output  resultLbl, Solution   ; output label and sum



                mov     eax, 0      ; exit with return code 0
        ret

_MainProc ENDP


;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
; Procedure takes base and exponent from main

Function PROC
    push ebp                      ; stores base pointer
    mov  ebp, esp                 ; set base pointer to current position

    mov ebx, [ebp + 12]   ; read base (2)
    mov ecx,  [ebp + 8]       ; read N



    MainLoop:           ; loop to calculate expoent
          mul ebx           ; multiply base by its self
          add eax, ebx           ; save answer in eax
          dec ecx                ; decrement the exponent
          cmp ecx, 0             ; check if exponent is 0 yet
          je exit                ; Exit if exponent is 0
          jg mainloop            ; stay in loop if exponent is above 0

    Exit: 

        pop ebp                   ; restore base pointer 
        ret

function ENDP



END                  

我希望程序产生正确的指数结果,并且如果可以计算上述序列,例如,如果给定3,则程序将计算(2 ^ 3 + 2 ^ 2 + 2 ^ 1 + 2 ^ 0)。

1 个答案:

答案 0 :(得分:4)

Base 2是一个非常特殊的情况,因为计算机使用二进制整数。

2^n = 1 << n,即2 n = 1个左移n次。即该位的位置已设置。

您要输入2^0 + 2^1 + ... 2^n一个数字,所有位都设置为包括一个n位。
2^(n+1)小1,所以您要的号码是 2^(n+1) - 1


x86有一条指令来设置寄存器bts reg, reg = Bit Test and Set中给定位置的位。在Intel CPU上,reg-reg形式解码为单个uop,其中1-周期延迟。 (https://agner.org/optimize/)还将位的旧值放入CF中,但通常我们对此并不关心。通常我们只将目标寄存器清零,然后使用BTS来设置单个位。

在Ryzen上,bts reg,reg是2 oups,而可变计数移位是单uop,所以mov edx, 1 / shl edx, cl也很好。但这对于代码大小来说更糟,而在Intel CPU上则更多。

在Intel Sandybridge系列CPU上,shl reg, cl解码为3 oups。 BMI2 shlx reg, reg, reg是1 uop,但是不幸的是,BMI2支持还远远不能想象得到。

(带有寄存器源的内存目标BTS速度很慢,将源视为索引到任意长度的位字符串,而不仅仅是寻址的dword,因此通常应避免使用它来提高性能。)


计算2^(n+1) - 1无溢出

({C version of this answer on another question,带有编译器输出。)

如果我们在将输入的数字输入1之前将其添加到BTS,它可能会绕回而无法为31工作(应该置位所有位,但会置位32%32 = 0

由于无论如何我们都需要在读取输入后需要一条额外的指令,因此我们可以使用x86的移位加法指令(LEA)再进行一次移位,因为我们减去1。因此,{{1 }}我们从高位开始,然后将其移至零。然后减去1即可根据需要设置所有位

n=31

其逻辑如下

xor    edx,edx                 ; edx = 0
bts    edx, eax                ; edx |=  1<<eax
lea    edx, [edx + edx - 1]    ; edx = (edx<<1) - 1

最后一列是第二列的总和,并非巧合。

具有3个“组件”寻址模式的LEA与简单的LEA(例如, Sandybridge系列的3个周期延迟与1个周期相比。但这仍然是一个单一的因素,因此它是吞吐量的绝佳选择。

如果我们真的想优化,并且不必担心n BTS result (2^n) *2 - 1 0 -> 1 -> 1 = 2 - 1 1 -> 2 -> 3 = 4 - 1 2 -> 4 -> 7 = 8 - 1 3 -> 8 -> 15 = 16 - 1 ... 30 -> 0x40000000 -> 0x7FFFFFFF = 0x80000000 - 1 31 -> 0x80000000 -> 0xFFFFFFFF = 0 - 1 大小写溢出,我们可以手动编写ASCII-> int循环,但以总计n=31而不是1,将0折叠成n+1。然后n会给我们bts,我们可以简单地2^(n+1)


无需将dec存储到内存中并重新加载;您已经在想要的位置将其保存在寄存器中。

您在exponent行上的注释是错误的,如果正确,您的代码将毫无意义。将ASCII转换为atod(例如C函数double)将以x87 strtod或SSE2 st0的形式返回,而不是EAX。 xmm0实际上代表ASCII(十进制)到整数。也许atod代表DWORD。

d

要实现 input Prompt, string, 40 ; read ASCII characters (N) atod string ; ascii (decimal) to integer in EAX xor edx,edx ; edx = 0 bts edx, eax ; edx |= 1<<eax lea edx, [edx + edx - 1] ; edx = (edx<<1) - 1 dtoa Solution, edx 需要2条指令,因此将其放入自己的函数中是很愚蠢的。仅仅传递args +调用函数就需要像使用1<<n那样的工作。

bts

编译器通常使用0 -> 1 -> 1 1 -> 2 -> 3 2 -> 4 -> 7 + mov edx,1 + mov ecx, eax。但这是错过的优化,尤其是在Intel CPU上。

BMI2 shl edx, cl会有所帮助(避免使用shlx edx, edx, ecx),但是其代码大小比mov-零+ xor差。而且BMI2并不总是可用。

如果您不能使编译器使用BTS(或者您的SHLX使用BMI2),则另一个不错的实现是bts,因此您可以将多余的移位烘烤成开始移位的数字。因此,(2ULL << n) - 1的计数可以将位移出并产生0。