尽可能快地将整数转换为汇编(64 位,NASM)中的字符串

Convert integer to string in assembly (64-bit, NASM) as fast as possible

我用 64 位 NASM 编写了一个 printint 函数,它可以将一个整数打印到 STDOUT。不过它真的很慢,在做了一些基准测试之后,我确定将整数转换为字符串是迄今为止最慢的部分。

我目前将整数转换为字符串的策略是这样的:

  1. 如果数字是0,跳到特殊情况。
  2. 如果是负数,打印一个负号,转成正整数,继续
  3. 创建一个 10 字节的缓冲区(足以容纳所有 32 位整数)和一个指向缓冲区后面的指针
  4. 检查数字是否为0;如果是,我们就完成了。
  5. 将数字除以10,将余数转换为ASCII
  6. 将余数放入缓冲区(从后往前)
  7. 减少缓冲区指针
  8. 循环回到第 4 步

我试过谷歌搜索其他人是怎么做的,它或多或少与我所做的相似,除以 10 直到数字为 0。

相关代码如下:

printint:                       ; num in edi
    push rbp                    ; save base pointer
    mov rbp, rsp                ; place base pointer on stack
    sub rsp, 20                 ; align stack to keep 20 bytes for buffering
    cmp edi, 0                  ; compare num to 0
    je _printint_zero           ; 0 is special case
    cmp edi, 0
    jg _printint_pos            ; don't print negative sign if positive

    ; print a negative sign (code not relevant)
    xor edi, -1                 ; convert into positive integer
    add edi, 1
_printint_pos:
    mov rbx, rsp                ; set rbx to point to the end of the buffer
    add rbx, 17
    mov qword [rsp+8], 0        ; clear the buffer
    mov word [rsp+16], 0        ; 10 bytes from [8,18)
_printint_loop:
    cmp edi, 0                  ; compare edi to 0
    je _printint_done           ; if edi == 0 then we are done
    xor edx, edx                ; prepare eax and edx for division
    mov eax, edi
    mov ecx, 10
    div ecx                     ; divide and remainder by 10
    mov edi, eax                ; move quotient back to edi
    add dl, 48                  ; convert remainder to ascii
    mov byte [rbx], dl          ; move remainder to buffer
    dec rbx                     ; shift 1 position to the left in buffer
    jmp _printint_loop
_printint_done:
    ; print the buffer (code not relevant)
    mov rsp, rbp                ; restore stack and base pointers
    pop rbp
    ret

我如何优化它才能运行更快?或者,是否有更好的方法将整数转换为字符串?

我不想使用 printf 或 C 标准库中的任何其他函数

原来我对瓶颈来源的理解是错误的。我的基准是有缺陷的。尽管魔术数字乘法和更好的循环等微优化确实有所帮助,但最大的瓶颈是系统调用。

通过使用缓冲读写(缓冲区大小为 16 kB),我实现了比 scanf 和 printf 更快地读取和打印整数的目标。

创建一个输出缓冲区使一个特定的基准测试速度提高了 4 倍以上,而微优化将其速度提高了大约 25%。

对于以后遇到此问题的任何人 post,以下是我所做的优化:

  1. 将所有 sys_write 调用替换为 write_buf 调用,其中 write_buf 将输出写入缓冲区,并且仅在缓冲区已满时打印缓冲区。 write_buf 函数的实现留给 reader.
  2. 作为练习。
  3. 用幻数乘法代替了除法,这在每次循环迭代中节省了几个时钟周期。
  4. 将 while 循环改为 do...while 循环,每次循环迭代节省一条 jmp 指令(随着时间的推移会增加很多)。
  5. 优化单个指令(例如使用 neg 代替 xor、add)并删除冗余指令。

我可以(但没有)做的另一个潜在改进是除以更大的基数并使用查找 table,正如 phuclv 在评论中提到的。