我正在尝试针对特定的 Kaby Lake CPU (i5-7300HQ) 优化以下子例程,理想情况下,与原始形式相比,代码至少要快 10 倍。该代码在 16 位实模式下作为软盘式引导加载程序运行。它在屏幕上显示一个十进制的十进制计数器,从 0 - 9999999999 计数然后停止。
我查看了 Microarchitecture 的 Agner 优化指南和 Assembly , Instruction Performance Table和英特尔的 Optimization Reference Manual .
到目前为止我能做的唯一明智的优化是交换 loop
dec + jnz
的说明, 解释 here .
另一种可能的优化可能是交换 lodsb
为 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
我的问题是 - 我该怎么做才能实现所需的速度提升?我还可以学习哪些其他 Material 来更好地理解基本概念?
注意:这个是 学校作业。虽然直接回答肯定会有所帮助,但我更希望得到相关研究 Material 的解释或指示,因为我们没有得到任何答复。
编辑:将代码更改为最小的可重现示例
最佳答案
这是我的看法。已应用以下优化:
此外,我已将代码更改为 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)。在现代系统上进行测试会有点困难,但我会尝试找到解决方案。
关于assembly - 优化第 7 代英特尔酷睿视频 RAM 中递增的 ASCII 十进制计数器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61460126/