assembly - 优化第 7 代英特尔酷睿视频 RAM 中递增的 ASCII 十进制计数器

标签 assembly optimization x86 intel bootloader

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

我查看了 Microarchitecture 的 Agner 优化指南和 Assembly , Instruction Performance Table和英特尔的 Optimization Reference Manual .

到目前为止我能做的唯一明智的优化是交换 loop dec + jnz 的说明, 解释 here .

另一种可能的优化可能是交换 lodsbmov + 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

我的问题是 - 我该怎么做才能实现所需的速度提升?我还可以学习哪些其他 Material 来更好地理解基本概念?

注意:这个学校作业。虽然直接回答肯定会有所帮助,但我更希望得到相关研究 Material 的解释或指示,因为我们没有得到任何答复。

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

最佳答案

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

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

  • 此外,我已将代码更改为 COM 二进制文件,以便于测试。将其重新转换为引导加载程序留给读者作为练习。一旦它成为引导加载程序,您可以做的一件事是修复代码,使 CSSS 具有 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)。在现代系统上进行测试会有点困难,但我会尝试找到解决方案。

    关于assembly - 优化第 7 代英特尔酷睿视频 RAM 中递增的 ASCII 十进制计数器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61460126/

    相关文章:

    c - 在 64 位汇编中编写函数

    c - 程序异常终止 Turbo C

    assembly - 如何开始逆向工程 z80 机器代码?

    python - 优化超大 csv 文件中的搜索

    assembly - 8086随机数生成器(不仅仅是使用系统时间)?

    x86-64 程序集的性能优化 - 对齐和分支预测

    c - 编写一段 C 代码,使编译器使用 SSE4.1 指令生成汇编代码

    matlab - 使用 bsxfun 加速 Matlab 嵌套 for 循环

    c - 堆栈帧在我的函数中是什么样子的?

    performance - 我可以在 x86/x86_64 上自动递增 16 位计数器吗?