一般来说,是否有必要或更容易按照每个注册的目的编码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:
;...
第二代码:
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:
;...
我使用的编译器 - Visual Studio 2015在执行此类任务时通常使用第二种方法,它不使用寄存器取决于它的用途,相反,编译器只选择基于其使用的寄存器' “易变”或“非易失”特征(在调用函数后)。因此,所有高级编程语言编程软件反汇编都使用第二种方法。
另一个有趣的事实是,在ARM语言中,GPR都具有相同的用途,并且被命名为R0-R7,这意味着当使用它时代码,代码将更类似于第二代码。
总而言之,我认为这两个代码使用相同的指令,因此无论我使用哪个寄存器,它都应该具有相同的速度。但我是对的吗?哪个代码更容易编码?
答案 0 :(得分:5)
遵循每个寄存器的目的主要是:
代码密度
例如,使用A
寄存器 1 通常会减少常用操作的代码大小,例如移动,算术,逻辑和IO 2 。
使用C
寄存器进行计数可让您利用jcxz
系列指令,避免明确比较
movsd
和类似的非常“密集”的指令,他们执行复杂的操作,否则将需要大量的代码。
然而code density doesn't mean "faster"由于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
要困难得多。
惯用语编程
许多说明(SP
,call
,ret
,...)假定使用push
作为堆栈指针。
可以避免使用SP
作为堆栈指针,但它不会非常惯用(也不高效)。
减少数据移动
在实模式下,只有少数寄存器可用作基址(其中一个是B
寄存器)。
从头开始在B
中保留地址会避免以后将其移入其中。虽然寄存器寄存器移动今天不需要执行单元,但它们使源更难以读取 4 。
大多数惯用寄存器用法今天已经放宽了 5 因为太多的特定用途寄存器会减少编译器可以做的优化(并且溢出到堆栈上的代价很高)。
CPU非常复杂,如果您想编写速度代码,那么您应该只考虑速度指标。惯用寄存器的使用不是其中之一,因为在微架构层面上there is not a single A
, B
or C
register所以“注册”的一件事就是程序员看到它们只是一个人类概念(好吧,还有一个前端概念)。 / p>
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
指令