assembly - 优化第 7 代英特尔酷睿视频 RAM 中的递增 ASCII 十进制计数器
问题描述
我正在尝试针对特定的 Kaby Lake CPU (i5-7300HQ) 优化以下子例程,理想情况下使代码与原始形式相比至少快 10 倍。该代码在 16 位实模式下作为软盘式引导加载程序运行。它在屏幕上显示一个十位十进制计数器,计数 0 - 9999999999,然后停止。
我查看了 Agner 的微架构和装配优化指南、 指令性能表和英特尔的优化参考手册。
到目前为止,我能够做的唯一明智的优化是将loop
指令交换为dec + jnz
,这里解释。
另一种可能的优化可能是交换lodsb
for 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
我的问题是——我能做些什么来达到预期的速度提升?我还可以学习哪些其他材料来更好地理解基本概念?
注意:这是学校作业。虽然直接的答案肯定会有所帮助,但我更感谢相关学习材料的解释或指示,因为我们没有得到任何帮助。
编辑:将代码更改为最小的可重现示例
解决方案
这是我的看法。已应用以下优化:
- 最低有效位已完全展开以获得最佳性能
- 其余数字已展开到每个数字一个部分
- 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)。在现代系统上进行测试会有点困难,但我会尝试找到解决方案。