- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
在Delphi math.pas 单元中,有一个过程 DivMod ,我想将其转换为内联并对其进行优化以使除数始终为10。但是我不知道五角大楼ASM的细节。什么是波纹管程序的转换
procedure DivMod(Dividend: Integer; Divisor: Word;
var Result, Remainder: Word);
asm
PUSH EBX
MOV EBX,EDX
MOV EDX,EAX
SHR EDX,16
DIV BX
MOV EBX,Remainder
MOV [ECX],AX
MOV [EBX],DX
POP EBX
end;
最佳答案
到目前为止,您可以做的最重要的优化是使用定点乘法逆来除以编译时常量Why does GCC use multiplication by a strange number in implementing integer division?。
任何体面的C编译器都会为您做到这一点,但是显然Delphi不会这样做,因此使用asm这样做是有充分理由的。
您可以在EAX中返回一个值,而不是将商和余数都存储到内存中吗?传递2个指针args似乎很浪费,并迫使调用者从内存中检索值。 (更新,是的,我认为您可以通过使其成为函数而不是过程来实现;不过,我只是从其他答案中盲目地修改了Delphi代码。)
无论如何,幸运的是,我们可以让C编译器为我们找出乘法逆和移位计数的艰苦工作。我们甚至可以使它使用与Delphi用于内联asm相同的“调用约定”。 GCC's regparm=3
32-bit calling convention在EAX,EDX和ECX中按此顺序传递args。
对于只需要商的情况,您可能需要制作一个单独的版本,因为(与慢速div
指令不同),如果使用快速乘法逆运算,则必须将其余部分作为x - (x/y)*y
分别计算。但是,是的,这仍然是现代x86的两倍至4倍。
或者,您可以将其余的计算留在纯Delphi中完成,除非编译器通常对优化不满意。
#ifdef _MSC_VER
#define CONVENTION _fastcall // not the same, but 2 register args are better than none.
#else
#define CONVENTION __attribute__((regparm(3)))
#endif
// use gcc -Os to get it to emit code with actual div.
divmod10(unsigned x, unsigned *quot, unsigned *rem) {
unsigned tmp = x/10;
// *quot = tmp;
*rem = x%10;
return tmp;
}
# gcc8.2 -O3 -Wall -m32
div10: # simplified version without the remainder, returns in EAX
mov edx, -858993459 # 0xCCCCCCCD
mul edx # EDX:EAX = dividend * 0xCCCCCCCD
mov eax, edx
shr eax, 3
ret
# quotient in EAX
# returns quotient in EAX, stores remainder to [ECX]
# quotient pointer in EDX is unused (and destroyed).
divmod10:
mov edx, -858993459
push ebx
mov ebx, eax
mul edx # EDX:EAX = dividend * 0xCCCCCCCD
mov eax, edx
shr eax, 3
# quotient in EAX = high_half(product) >> 3 = product >> (32+3)
lea edx, [eax+eax*4] # EDX = quotient*5
add edx, edx # EDX = quot * 10
sub ebx, edx # remainder = dividend - quot*10
mov DWORD PTR [ecx], ebx # store remainder
pop ebx
ret
# quotient in EAX
quotient
指针输入,则可以调整asm或在Godbolt上调整C并查看新的编译器输出。
div
的指令更多,但
div
的速度非常慢(10 oups,即使在Skylake上也有26个周期的延迟)。
$CCCD
用作乘法逆。
div
指令更好。请参阅
https://agner.org/optimize/和其他性能链接
in the x86 tag wiki。
{$IFDEF CPUX64}
块中,最好的选择可能是使用
UInt64(3435973837)*num;
的纯帕斯卡
function Div10(Num: Cardinal): Cardinal;
{$IFDEF PUREPASCAL}
begin
Result := Num div 10;
end;
{$ELSE !PUREPASCAL}
{$IFDEF CPUX86}
asm
MOV EDX, $CCCCCCCD
MUL EDX // EDX:EAX = Num * fixed-point inverse
MOV EAX,EDX // mov then overwrite is ideal for Intel mov-elimination
SHR EAX,3
end;
{$ENDIF CPUX86}
{$IFDEF CPUX64}
asm
// TODO: use pure pascal for this; Uint64 is efficient on x86-64
// Num in ECX, upper bits of RCX possibly contain garbage?
mov eax, ecx // zero extend Num into RAX
mov ecx, $CCCCCCCD // doesn't quite fit in a sign-extended 32-bit immediate for imul
imul rax, rcx // RAX = Num * fixed-point inverse
shr rax, 35 // quotient = eax
end;
{$ENDIF CPUX64}
{$ENDIF}
{Remainder is the function return value}
function DivMod10(Num: Cardinal; var Quotient: Cardinal): Cardinal;
{$IFDEF PUREPASCAL}
begin
Quotient := Num div 10;
Result := Num mod 10;
end;
{$ELSE !PUREPASCAL}
{$IFDEF CPUX86}
asm
// Num in EAX, @Quotient in EDX
push esi
mov ecx, edx // save @quotient
mov edx, $CCCCCCCD
mov esi, eax // save dividend for use in remainder calc
mul edx // EDX:EAX = dividend * 0xCCCCCCCD
shr edx, 3 // EDX = quotient
mov [ecx], edx // store quotient into @quotient
lea edx, [edx + 4*edx] // EDX = quot * 5
add edx, edx // EDX = quot * 10
mov eax, esi // off the critical path
sub eax, edx // Num - (Num/10)*10
pop esi
// Remainder in EAX = return value
end;
{$ENDIF CPUX86}
{$IFDEF CPUX64}
asm
// TODO: use pure pascal for this? Uint64 is efficient on x86-64
// Num in ECX, @Quotient in RDX
mov r8d, ecx // zero-extend Num into R8
mov eax, $CCCCCCCD
imul rax, r8
shr rax, 35 // quotient in eax
lea ecx, [rax + 4*rax]
add ecx, ecx // ecx = 10*(Num/10)
mov [rdx], eax // store quotient
mov eax, r8d // copy Num again
sub eax, ecx // remainder = Num - 10*(Num/10)
// we could have saved 1 mov instruction by returning the quotient
// and storing the remainder. But this balances latency better.
end;
{$ENDIF CPUX64}
{$ENDIF}
mov
移出关键路径,并使用不同的寄存器来避免REX前缀。并用一个ADD代替一个LEA。
unsigned divmod10(unsigned x, unsigned *quot) {
unsigned qtmp = x/10;
unsigned rtmp = x%10;
*quot = qtmp;
//*rem = rtmp;
return rtmp;
}
imul r64,r64
在Intel CPU和Ryzen上速度更快(3个周期延迟/1 uop)。
mul r32
是3 oups,在Sandybridge系列上每2个时钟只有1个吞吐量。我认为乘法硬件自然会产生128位结果,并将其低64位拆分为edx:eax需要额外的uop或类似的东西。
关于delphi - 如何将DivMod优化为10的常数除数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53038619/
我是一名优秀的程序员,十分优秀!