J.S*_*ith 3 c++ optimization x86 assembly cpu-registers
一般来说,按照每个寄存器的用途编码x86汇编是否必要或更容易?
x86架构中的寄存器每个都是首先设计用于特殊目的,但现代编译器似乎并不关心它们的使用(除非在某些特殊条件下,例如REP MOV或MUL).
那么,取决于每个寄存器的用途,代码是更容易还是更优化?(不管与某些寄存器相同的特殊指令(或编码))
例如(我可以改用REP MOVSB或LODSB STOSB,但只是为了演示):
第一个代码:
LEA ESI,[AddressOfSomething]
LEA EDI,[AddressOfSomethingElse]
MOV ECX,NUMBER_OF_LOOP
LoopHere:
MOV AL,[ESI]
ADD AL,8
MOV [EDI],AL
ADD ESI,1
ADD EDI,1
CMP AL,0
JNZ LoopHere
TheEnd:
;...
Run Code Online (Sandbox Code Playgroud)
第二代码:
LEA ECX,[AddressOfSomething]
LEA EDX,[AddressOfSomethingElse]
MOV EBX,NUMBER_OF_LOOP
LoopHere:
MOV AL,[ECX]
ADD AL,8
MOV [EDX],AL
ADD ECX,1
ADD EDX,1
CMP AL,0
JNZ LoopHere
TheEnd:
;...
Run Code Online (Sandbox Code Playgroud)
我使用的编译器 - Visual Studio 2015在执行这样的任务时通常使用第二种方法,它不使用寄存器取决于它的用途,相反,编译器只根据其"volatile"选择使用哪个寄存器或"非易失性"特征(在调用函数后).因此,所有高级编程语言编程软件反汇编都使用第二种方法.
另一个有趣的事实是,在ARM语言中,GPR都具有相同的用途,并且被命名为R0-R7,这意味着当代码使用它时,代码将更类似于第二代码.
总而言之,我的观点是这两个代码使用相同的指令,因此无论我使用哪个寄存器,它都应该具有相同的速度.但我是对的吗?哪个代码更容易编码?
遵循每个登记册的目的主要是:
代码密度
例如,使用A寄存器1通常会减少常用操作(如移动,算术,逻辑和IO 2)的代码大小.
使用C寄存器进行计数可以利用jcxz指令族,避免显式比较.
movsd类似的是非常"密集"的指令,它们执行复杂的操作,否则将需要大量的代码.
然而,由于x86明显是CISC,因此代码密度并不意味着"更快",复杂指令可能比同等系列的更简单指令需要更多时间来执行3.
可读性
rep movsd有效的指令是编码将数据从源移动到目的地的循环的"高级"方式.
解析循环
push eax
pushf
.loop:
mov eax, DWORD [esi]
mov DWORD [es:edi], eax
add esi, 4*(1-D*2)
add edi, 4*(1-D*2)
dec ecx
jnz .loop
popf
pop eax
Run Code Online (Sandbox Code Playgroud)
要困难得多.
惯用语编程
采用SP作为堆栈指针是由很多的指令(假定call,ret,push,...).
可以避免使用SP堆栈指针,但它不会非常惯用(也不高效).
数据移动量减少
在实模式中,只有少数寄存器可用作基址(其中一个是B寄存器).从头开始
保持地址B将避免以后将它们移入其中.虽然寄存器寄存器移动今天不需要执行单元,但它们使源更难以读取4.
大多数惯用寄存器用法今天已经放宽了5,因为太多的特定用途寄存器会减少编译器可以做的优化(并且溢出到堆栈上的代价很高).
CPU非常复杂,如果您想编写速度代码,那么您应该只考虑速度指标.惯用寄存器的使用不是其中之一,因为有一点没有单一A,B或者C在微架构层面注册,所以"注册"程序员看到它们只是一个人类概念(好吧,和前端概念) ).
1在其形式AL,AX,EAX,RAX
2 mov A, [mem]使用操作码A0或A1,而mov B, [mem]使用8A 1E或8B 1E.对于add和类似的情况也是如此.in,out,div,mul强制使用A.
3但不要取和解码.
4数据移入寄存器是否有相当于"意大利面条代码"?
5考虑例如各种寻址模式或imul指令
| 归档时间: |
|
| 查看次数: |
121 次 |
| 最近记录: |