Assembly 提高二进制文件的效率->;8086的格雷码
我是汇编的初学者,这是我设计用来将二进制和灰色转换成十六进制的代码,并以十六进制打印生成的位模式Assembly 提高二进制文件的效率->;8086的格雷码,assembly,hex,x86-16,micro-optimization,gray-code,Assembly,Hex,X86 16,Micro Optimization,Gray Code,我是汇编的初学者,这是我设计用来将二进制和灰色转换成十六进制的代码,并以十六进制打印生成的位模式 mov al, a mov bl, al shr bl, 1 xor al, bl 虽然程序正在运行,但我想学习其他更简单的方法来提高效率,我尝试了许多其他方法,但它影响了输出。(这个答案是根据问题的第一个版本编写的,其中有一些尚未
mov al, a
mov bl, al
shr bl, 1
xor al, bl
虽然程序正在运行,但我想学习其他更简单的方法来提高效率,我尝试了许多其他方法,但它影响了输出。(这个答案是根据问题的第一个版本编写的,其中有一些尚未优化的十六进制打印代码,它是一个.exe程序的完整源代码。问题的更新删除了唯一有空间优化的部分,除了与8086无关的ILP,所以我不打算删除这些部分。)(请回答。)
代码大小优化(与8086尤其是8088上的速度相关,请参阅):
- bin2Gray部件:没有变化,除非您计算从mem重新加载或使
恒定。只需重新排序奔腾和更高版本上的ILP指令。或者可能是a
的表格xlat
- 字节->十六进制数字:21个字节,低于32个(更少的代码提取字节)
- 退出:从4字节(mov ah/int)减少1字节(
)。至少适用于ret
可执行文件,这些文件也更小.com
int 10h
除外
int 20h
也可以退出(无需任何AH设置),但只能从.com
可执行文件中退出。我感兴趣的部分是使用紧凑的代码计算所需寄存器中的ASCII数字,但如果您想要一个小的整体程序,则可以使用.com
进行退出。这也可以避免设置DS。(虽然如果将a
和eq或=
设为常量,则不需要设置DS。)
不尝试:利用初始寄存器值,这在某些DOS版本中显然是可靠的。如果你假装正在编写一个块,作为更大程序的一部分可能有用,这是不可行的
您的程序基本上有两个独立的部分:计算一个格雷码,以及在寄存器中计算一个1字节值的bin->hex。单独提取半字节并不能有效地向后优化格雷码计算,因此我认为我们可以将它们完全分开
有(连续值之间只有一位翻转)。好吧,
x^(x>>1)
是从二进制计算的最便宜的方法,但这不是不可能的,只要在寄存器中输入2条指令,就可以完成某些事情
同样相关:对于gray->binary指出标准的x^(x>>1)
是GF(2k)中的乘法。因此,在最近使用Galois字段指令的CPU上,我认为您可以使用一次16字节(使用固定多项式,我认为这不是我们想要的)
8088性能主要与内存访问有关(包括代码提取) 显示指令计时,但这些计时不是代码提取。8088有一个4字节的预取缓冲区(只有一个8位数据总线),在8086上有一个6字节的16位总线。Supercat在回答中建议: 在最初的8088处理器上,估计执行速度的最简单方法通常是忽略周期计数,而是对内存访问(包括指令获取)进行计数,然后乘以4 我认为8086也差不多,只是每次访问都可以是一个完整的2字节字,所以直线代码(没有分支)一次可以获取2字节 为了简单起见,我只是用表中的指令大小和周期计数注释了asm源代码,而没有尝试对预取缓冲区的行为进行建模 (如
al=ds:[bx+al]
)只有1个字节,如果你不介意有一个256字节的表,它就值得使用。它需要11个字节来执行,但这包括它所进行的数据访问。不算代码获取,mov-bl,al
/shr-al,1
/xor-al,bl
是2+2+3个周期,但代码大小的3个字将需要12个周期来获取。xlat几乎占用了很长一段时间,但当它完成预取缓冲区将有一段时间来获取以后的指令,所以我认为这更像是一场胜利
尽管如此,它还是要求该表来自某个地方,或者在加载可执行文件时来自磁盘,或者您必须对其进行预计算。并且您需要将一个指针指向BX,因此只有在循环中这样做,它才可能是一个胜利
但是如果您使用的是表格,您可以将问题的两个部分结合起来,查找给定二进制文件的格雷码的ASCII十六进制字符,例如,使用mov-dx、[bx+si]
,表格指针为si,二进制字节为BL,BH=0。(dx设置为通过DOS调用输出DL。)当然,这需要您的表为256个字(512字节)。拥有一个小的可执行文件可能比在这里保存几个周期更有价值;屏幕或文件的实际I/O速度可能足够慢,因此不会有太大影响。但是,如果您要对多个字节执行此操作,则将ASCII字节对复制到缓冲区中可能会很好
有一个优化将有助于更现代的CPU(从奔腾开始),它可以并行运行多条指令:复制寄存器,然后移动原始寄存器,这样可以在复制的同一周期内进行
; optimized for Instruction-level Parallelism
;; input: AL output: AL = bin_to_gray(AL)
;; clobbers: DL
mov dl, al ; 2B 2 cycles (not counting code-fetch bottlenecks)
shr al, 1 ; 2B 2c
xor al, dl ; 2B 3c
(有关现代CPU的更多信息,请参阅。另外,-mov消除在字节或字寄存器上不起作用,因为这会合并到EDX的低位。因此,即使在具有mov消除功能的CPU上,通常也是如此。)
; a equ 0ACh ; makes the following instructions 2 bytes each
;;; otherwise, with a being static storage, loading from memory twice sucks
mov al, a
shr al, 1 ; 2B, 2 cycles
xor al, a ; reg,imm: 2B, 4 cycles on 8088. reg,mem: 3B, 13+6 cycles
;; input: byte in AL. output: print 2 ASCII hex digits with BIOS int 10h
;; clobbers: CX, DX
hexprint_byte:
mov ah, 0Eh ; BIOS teletype call #
; push ax ; 1B 15c
mov dx, ax ; 2B 2c ; save number, and AH=call number
mov cl, 4 ; 2B 4c
shr al, cl ; 2B 8+4*4 cycles isolate the high nibble
.loop:
cmp al, 10 ; 2B 4c set CF according to digit <= 9
sbb al, 69h ; 2B 4c read CF, set CF and conditionally set AF
das ; 1B 4c magic, which happens to work
int 10h ; 2B BIOS teletype output (AL), no return value
; pop ax ; 1B 12c ; would do one extra pop if you used this instead of mov/xchg, so you'd need jmp ax instead of ret. But AND destroys AX
xchg ax, dx ; 1B 3c ; retrieve the original again (with AH=0Eh call number)
and al, 0Fh ; 2B 4c ; isolate the low nibble this time
dec cx ; 1B 3c ; PF is set from the low byte only, CH garbage isn't a problem.
jpe .loop ; 2B 4c-not-taken, 16c-taken
; 4-1 = 3, (0b11) which has even parity
; so JPE is taken the first time, falls through the 2nd
;; size = 21 bytes
aam 16 ; 83 cycles, 2 bytes AH= quotient = leading digit AL = remainder = trailing digit (low)
; normally never use div or aam by a power of 2, only for code-size over speed.
cmp al, 10 ; 2B 4c set CF according to digit <= 9
sbb al, 69h ; 2B 4c read CF, set CF and conditionally set AF
das ; 1B 4c magic, which happens to work
xchg dx, ax ; 1B 3c stash low digit in DL
mov al, dh ; 2B 2c get leading digit
cmp al, 10 ; 2B 4c
sbb al, 69h ; 2B 4c most significant (high) nibble as ASCII hex
das ; 1B 4c
mov ah, 0Eh ; 2B 3c BIOS teletype output (of AL), advancing cursor
int 10h ; 2B ?
mov al, dl ; 2B 2c ; get low digit back from DL xchg ax, dx breaks AH callnum
int 10h ; 2B
; size=23B