我想使用专门针对32位操作数运行的DivMod
函数。 implementation in the RTL返回16位变量中的值。它的声明是:
procedure DivMod(Dividend: Cardinal; Divisor: Word; var Result, Remainder: Word);
所以,我不能使用它,因为我的输入可能会溢出返回值。
天真的Pascal实现如下所示:
procedure DivMod(Dividend, Divisor: Cardinal; out Quotient, Remainder: Cardinal);
begin
Quotient := Dividend div Divisor;
Remainder := Dividend mod Divisor;
end;
这很有效,但两次进行分组。由于我的代码的一部分调用了函数,这是一个性能瓶颈,我只想执行一次除法。为此,我使用了Serg的32位DivMod来自这个问题:Is there a DivMod that is *not* Limited to Words (<=65535)?
procedure DivMod(Dividend, Divisor: Cardinal; out Quotient, Remainder: Cardinal);
asm
PUSH EBX
MOV EBX,EDX
XOR EDX,EDX
DIV EBX
MOV [ECX],EAX
MOV EBX,Remainder
MOV [EBX],EDX
POP EBX
end;
这很有效。
但是现在我想要一个64位代码的函数版本。请注意,我仍然希望对32位操作数进行操作,并返回32位值。
我应该使用64位汇编程序重写函数,还是使用运行的RTL的DivMod
重载并返回64位值就足够了?
具体来说,我想知道在编写执行32位操作的64位代码时是否有性能优势。这甚至可能吗?或者我是否会最终使用DivMod
参数重新实施UInt64
重载?如果值得实现一个定制的64位asm版本,我将如何去做,注意操作数和操作是32位。
我认为它看起来像这样,但我不是专家,可能会出错:
procedure DivMod(Dividend, Divisor: Cardinal; out Quotient, Remainder: Cardinal);
asm
MOV EAX,ECX // move Dividend to EAX
MOV ECX,EDX // move Divisor to ECX
XOR EDX,EDX // zeroise EDX
DIV ECX // divide EDX:EAX by ECX
MOV [R8],EAX // save quotient
MOV [R9],EDX // save remainder
end;
答案 0 :(得分:7)
对于总是除以10(每条评论)的特殊情况,您可以执行以下操作:
procedure DivMod10(num : Cardinal; var q, r : Cardinal); inline;
var
rl : uInt64;
begin
rl := UInt64(3435973837)*num;
q := rl shr 35;
r := num - q*10;
end;
算法因分母而异,但确定它的来源和幻数可以在libdivide中找到。这对于所有无符号32位整数都是准确的,并且比使用div
快3倍(并提供余数)。
基准(优化):
t0 := GetTickCount;
for I := 1 to 999999999 do begin
DivMod10(i, q, r);
end;
ShowMessage(IntToStr(GetTickCount - t0)); // result : 1809
t0 := GetTickCount;
for I := 1 to 999999999 do begin
q := i div 10;
end;
ShowMessage(IntToStr(GetTickCount - t0)); // result : 5336
测试:
for I := 1 to High(Cardinal) do begin
DivMod10(i,q,r);
if q <> (i div 10) then WriteLn(IntToStr(i));
// no mismatch found
end;
答案 1 :(得分:2)
UInt64
版本之上实现它是完全合理的。这看起来像这样:
procedure DivMod(Dividend, Divisor: Cardinal; out Quotient, Remainder: Cardinal);
var
Quotient64, Remainder64: UInt64;
begin
DivMod(Dividend, Divisor, Quotient64, Remainder64);
Quotient := Quotient64;
Remainder := Remainder64;
end;
与最佳asm版本相比,我认为性能不会受到太大影响。
但是,我认为问题中的x64 asm代码是正确的。 MOV
指令对32位操作数都很好。并且DIV
也如asm代码中的注释中所述。 DIV r/m32
的英特尔documentation说:
无符号除法EDX:E / r / m32,结果存储在EAX←商,EDX←剩余。
让我们看一下Delphi编译器对此代码的作用:
var
a, b, c, d: Cardinal;
....
a := 666;
b := 42;
c := a div b;
d := a mod b;
生成的代码是:
Project39.dpr.14: a := 666; 0000000000423A68 C7450C9A020000 mov [rbp+$0c],$0000029a Project39.dpr.15: b := 42; 0000000000423A6F C745082A000000 mov [rbp+$08],$0000002a Project39.dpr.16: c := a div b; 0000000000423A76 8B450C mov eax,[rbp+$0c] 0000000000423A79 33D2 xor edx,edx 0000000000423A7B F77508 div dword ptr [rbp+$08] 0000000000423A7E 894504 mov [rbp+$04],eax Project39.dpr.17: d := a mod b; 0000000000423A81 8B450C mov eax,[rbp+$0c] 0000000000423A84 33D2 xor edx,edx 0000000000423A86 F77508 div dword ptr [rbp+$08] 0000000000423A89 895500 mov [rbp+$00],edx
我没有任何期望32位除法比64位除法更有效,但这并不重要。使用32位操作数执行32位操作似乎更自然。