首页 > 解决方案 > 优化第 7 代英特尔酷睿视频 RAM 中的递增 ASCII 十进制计数器

问题描述

我正在尝试针对特定的 Kaby Lake CPU (i5-7300HQ) 优化以下子例程,理想情况下使代码与原始形式相比至少快 10 倍。该代码在 16 位实模式下作为软盘式引导加载程序运行。它在屏幕上显示一个十位十进制计数器,计数 0 - 9999999999,然后停止。

我查看了 Agner 的微架构装配优化指南、 指令性能表和英特尔的优化参考手册

到目前为止,我能够做的唯一明智的优化是将loop指令交换为dec + jnz这里解释。

另一种可能的优化可能是交换lodsbfor mov + dec,但我发现的信息是相互矛盾的,有人说它有一点帮助,而另一些人说它实际上可能会损害现代 CPU 的性能。

我还尝试切换到 32 位模式并将整个计数器保持在一个未使用的寄存器对中以消除任何内存访问,但在读到它之后我意识到这十位将立即被缓存以及 L1 缓存之间的延迟差异和寄存器只有大约三倍,所以绝对不值得以这种格式使用计数器的额外开销。

(编者注:add reg延迟为 1 个周期,add [mem]延迟约为 6 个周期,包括 5 个周期的存储转发延迟。或者如果[mem]像视频 RAM 那样不可缓存,则更糟。)

org 7c00h

pos equ 2*(2*80-2)  ;address on screen

;init
cli
mov ax,3
int 10h
mov ax,0b800h
mov es,ax
jmp 0:start

start:
    push cs
    pop ds
    std

    mov ah, 4Eh
    xor cx, cx
    mov bl,'9'

countloop:
    mov cl,10           ;number of digits to add to
    mov si,counter+9    ;start of counter
    mov di,pos          ;screen position

    stc                 ;set carry for first adc
next_digit:
    lodsb               ;load digit
    adc al,0
    cmp bl, al
    jnc print
    add al,-10          ;propagate carry if resulting digit > 9
print:
    mov [si+1],al       ;save new digit
    stosw               ;print

    ;replaced loop with a faster equivalent
    ;loop next_digit
    dec cl
    jnz next_digit

    jnc countloop

    jmp $

counter:
    times 10 db '0'

    times 510-($-$$) db 0
    dw 0aa55h

我的问题是——我能做些什么来达到预期的速度提升?我还可以学习哪些其他材料来更好地理解基本概念?

注意:这学校作业。虽然直接的答案肯定会有所帮助,但我更感谢相关学习材料的解释或指示,因为我们没有得到任何帮助。

编辑:将代码更改为最小的可重现示例

标签: assemblyoptimizationx86intelbootloader

解决方案


这是我的看法。已应用以下优化:

  • 最低有效位已完全展开以获得最佳性能
  • 其余数字已展开到每个数字一个部分
  • BCD 算法已用于将代码减少到每个数字一个条件分支
  • 段使用已被改组以减少使用的前缀数量
  • 指令顺序已优化,可将长延迟指令移出关键路径

此外,我已将代码更改为 COM 二进制文件,以便于测试。将其转回引导加载程序留给读者作为练习。CS一旦它是引导加载程序,您可以做的一件事是修复代码,使其SS具有0000. 这避免了对某些微架构上的加载和存储的惩罚。

        org     100h

pos     equ     2*(2*80-12)             ; address on screen

        mov     ax, 3                   ; set up video mode
        int     10h
        mov     ax, 0b800h
        mov     ds, ax
        mov     es, ax

        mov     di, pos
        mov     ax, 4e30h               ; '0' + attribute byte 4e
        mov     cx, 10
        cld
        rep     stosw                   ; set up initial display

        xor     ax, ax
        sub     sp, 10
        push    ax
        push    ax
        push    ax
        push    ax
        push    ax
        mov     bp, sp                  ; set up counter

        dec     di
        dec     di                      ; di points to the last digit on screen
        mov     bx, digits              ; translation table

        jmp     countloop

%macro  docarry 1                       ; digits other than the last one
        mov     al, [bp+%1]             ; second to last digit
        inc     ax                      ; add carry to al
        aaa                             ; generate BCD carry
        mov     [bp+%1], al             ; desposit to counter
        cs xlat                         ; generate ASCII digit
        mov     [di-2*9+2*%1], al       ; display digit
        jnc     countloop               ; exit when carry dies
%endm

docarry2:                               ; place this here so jumps are in range
        docarry 2
        docarry 1
        docarry 0
        int     20h

        align   16                      ; for performance
countloop:
        mov     [di], byte '0'          ; treat last digit separately
        mov     [di], byte '1'
        mov     [di], byte '2'
        mov     [di], byte '3'
        mov     [di], byte '4'
        mov     [di], byte '5'
        mov     [di], byte '6'
        mov     [di], byte '7'
        mov     [di], byte '8'
        mov     [di], byte '9'

        docarry 8
        docarry 7
        docarry 6
        docarry 5
        docarry 4
        docarry 3
        jmp     docarry2

digits:
        db      '0123456789'

与我基于 8 MHz 80286 的机器上的原始代码相比,这将速度提高了约 30 倍,并设法使计数器每秒增加约 329000 次(每个数字约 3.04 µs)。在现代系统上进行测试会有点困难,但我会尝试找到解决方案。


推荐阅读