我想使用一个专门在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,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位代码时是否有性能优势.这有可能吗?或者我最终会用UInt64参数重新实现DivMod重载?如果值得实现一个定制的64位asm版本,我将如何去做,注意操作数和操作是32位.
我认为它看起来像这样,但我不是专家,可能有错误:
procedure DivMod(Dividend,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;
解决方法
对于总是除以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,r); if q <> (i div 10) then WriteLn(IntToStr(i)); // no mismatch found end;