对于我的BigIntegers,在PUREPASCAL实现中(即不允许汇编程序),我必须乘以两个UInt32
才能获得UInt64
结果。
通常的做法是扩展至少一个操作数,这样就得到64位乘法:
Res := UInt64(A) * B;
其中Res
为UInt64
而A
和B
为UInt32
。
但是,在Win32中,这会产生一个相当笨重的机器代码:
MulTest.dpr.431: Res := UInt64(A) * B;
004DB463 8B45F8 mov eax,[ebp-$08] // load A
004DB466 33D2 xor edx,edx // make it UInt64
004DB468 52 push edx // push A
004DB469 50 push eax
004DB46A 8B45FC mov eax,[ebp-$04] // load B
004DB46D 33D2 xor edx,edx // make it UInt64
004DB46F E87C0AF3FF call @_llmul // 64 bit multiplication
004DB474 8945E8 mov [ebp-$18],eax // store 64 bit result
004DB477 8955EC mov [ebp-$14],edx
现在,如果您这样做:
Res := A * B;
不幸的是,你获得了32位的中间结果(实际结果的前32位被简单地清零):
MulTest.dpr.435: Res := A * B;
004DB4BD 8B45FC mov eax,[ebp-$04]
004DB4C0 F76DF8 imul dword ptr [ebp-$08]
004DB4C3 33D2 xor edx,edx // zero out top 32 bits
004DB4C5 8945E8 mov [ebp-$18],eax
004DB4C8 8955EC mov [ebp-$14],edx
现在,如果行xor edx,edx
不存在,那么结果将正是我所需要的。这比使用UInt64
演员的加宽版本快两倍(即花费不到一半的时间)。
问题:有没有人知道是否存在不丢弃64位结果的前32位的伪函数或技巧或演员?我知道如何在汇编程序中执行此操作,但这必须是PUREPASCAL(它也应该适用于其他平台)。
我设法通过访问32位未分配整数数组,在PUREPASCAL中更快地生成32位加法,这些整数组成BigInteger作为无符号16位整数数组并添加它们。所以我也尝试使用16位中间结果进行乘法运算:
// Too slow: in a test, 2973 ms for Mul32(A, B) vs 1432 ms for UInt64(A) * B.
function MulU32ToU64(L, R: UInt32): UInt64; inline;
var
L0R0, L0R1, L1R0, L1R1, Sum: UInt32;
type
TUInt64 = packed record
case Byte of
0: (L0, L1, L2, L3: UInt16);
1: (I0, I1: UInt32);
end;
TUInt32 = packed record
Lo, Hi: Word;
end;
begin
L0R0 := TUInt32(L).Lo * TUInt32(R).Lo;
L0R1 := TUInt32(L).Lo * TUInt32(R).Hi;
L1R0 := TUInt32(L).Hi * TUInt32(R).Lo;
L1R1 := TUInt32(L).Hi * TUInt32(R).Hi;
TUInt64(Result).L0 := TUInt32(L0R0).Lo;
Sum := UInt32(TUInt32(L0R0).Hi) + TUInt32(L1R0).Lo + TUInt32(L0R1).Lo;
TUInt64(Result).L1 := TUInt32(Sum).Lo;
Sum := UInt32(TUInt32(Sum).Hi) + TUInt32(L1R0).Hi + TUInt32(L0R1).Hi + L1R1;
TUInt64(Result).I1 := Sum;
end;
它给了我正确的结果,但是慢的结果是UInt64(A) * B
的两倍多。这并不奇怪,因为它进行了4次UInt32乘法和大量添加,这使得它比使用System.__llmul
的代码慢。
正如@J ...指出的那样,Delphi通常使用IMUL
,它进行有符号的乘法运算。所以乘以例如$00000002
和$FFFFFFFF
会产生EAX = $FFFFFFFE
和EDX = $FFFFFFFF
(换句话说,Int64
的值为-2
),而我需要{{} 1}}(相同),但EAX = $FFFFFFFE
(一起EDX = $00000001
,值UInt64
)。因此,排除前32位是正确的,似乎没有办法强迫Delphi使用$00000001FFFFFFFE
并保留结果的前32位。
答案 0 :(得分:6)
MulTest.dpr.435: Res := A * B;
004DB4BD 8B45FC mov eax,[ebp-$04]
004DB4C0 F76DF8 imul dword ptr [ebp-$08]
004DB4C3 33D2 xor edx,edx // zero out top 32 bits
004DB4C5 8945E8 mov [ebp-$18],eax
004DB4C8 8955EC mov [ebp-$14],edx
现在,如果xor edx,edx不存在,那么结果将是我所需要的。
不,它根本不是你想要的。这是一个有符号的乘法,如果你想要一个无符号的结果,结果是无意义的。制作A:=$FFFFFFFF
和B:=2
- imul
的结果为EAX = FFFFFFFE
和EDX = FFFFFFFF
。即使使用两个无符号操作数,也会发出此操作码。您需要mul
指令,而不是imul
。我不认为delphi编译器会从纯粹的pascal中发出mul
。来自the documentation on *
(强调我的)
无论x和y的类型如何,x / y的值都是Extended类型。对于其他算术运算符,只要至少一个操作数是实数,结果就是Extended类型;否则,当至少一个操作数是Int64类型时,结果是Int64类型; 否则,结果为Integer类型。
整数 - 已签名。鉴于这对于架构的特性有多依赖,并且考虑到delphi编译器的不足,我认为这里唯一的高性能解决方案将是依赖于目标的汇编。
function UMul3264(x, y : UInt32) : UInt64;
asm
mul eax, edx
end;
答案 1 :(得分:0)
有一个 Windows 宏 UInt32x32To64(a, b) 可以将两个无符号 32 位值相乘并得到 64 位结果。
如果您需要纯 pascal,则必须将两个 32 位无符号值分配给 64 位无符号值,然后将它们相乘。
function UInt32x32To64(x, y: UInt32): UInt64;
var
xl, yl: UInt64;
begin
xl := x;
yl := y;
Result := xl * yl;
end;
这是检查此函数的代码示例。这段代码也有一个汇编函数只是为了比较。您不需要它,因为您有 PurePascal,但它的实现非常有效 - 只需一个 mul
指令。它是 mul
的一种特殊形式,它只接受一个参数,另一个来自 eax
寄存器。生成的 64 位值存储在 edx:eax 中。因此它的实现比将 32 位值转换为 64 位然后将它们相乘更有效,因为 Delphi 从 System.pas 调用 __llmul
,它执行 3 个 mul
指令,每个指令成本很高。
program TestMultiply;
{$APPTYPE CONSOLE}
function UInt32x32To64_asm(x, y: UInt32): UInt64; assembler;
asm
{$IFDEF WIN32}
mul edx
{$ELSE}
mov eax, ecx
mul edx
shl rdx, 32
or rax, rdx
{$ENDIF}
end;
function UInt32x32To64_purepascal(x, y: UInt32): UInt64;
var
xl, yl: UInt64;
begin
xl := x;
yl := y;
Result := xl * yl;
end;
var
aa, bb: UInt32;
cc, cc_test: UInt64;
begin
aa := 2147483647;
bb := 2148736590;
cc_test := 4614376688735543730;
cc := UInt32x32To64_asm(aa, bb);
if cc <> cc_test then
WriteLn('Error')
else
WriteLn('OK');
cc := UInt32x32To64_purepascal(aa, bb);
if cc <> cc_test then
WriteLn('Error')
else
WriteLn('OK');
end.
这是要检查的 Python3 代码:
aa = 2147483647
bb = 2148736590
cc = aa * bb
print(cc)
print("Error") if cc != 4614376688735543730 else print("OK!")