delphi – 检查64位乘以常数的参数
对于我的BigInteger代码,对于非常大的BigIntegers,输出结果很慢.所以现在我使用一个递归的分而治之算法,它仍然需要2’30“才能将当前最大的已知素数转换为超过2200万位数的十进制字符串(但只有135 ms将其转换为十六进制字符串) .
我仍然想减少时间,所以我需要一个例程,可以将NativeUInt(即32位平台上的UInt32,64位平台上的UInt64)除以100非常快.所以我使用乘法乘法.这在32位代码中工作正常,但我对64位不是100%肯定. 所以我的问题是:有没有办法检查乘以无符号64位值的乘法结果的可靠性?我通过简单地尝试使用UInt32的所有值(0 .. $FFFFFFFF)来检查32位值.这花费了大约. 3分钟.检查所有UInt64将花费比我的生命更长的时间.有没有办法检查使用的参数(恒定,换档后)是否可靠? 我注意到如果选择的参数错误(但接近),DivMod100()总是因为4000004B这样的值而失败.是否有特殊值或范围来检查64位,所以我不必检查所有值? 我目前的代码: const {$IF DEFINED(WIN32)} // Checked Div100Const = UInt32(UInt64($1FFFFFFFFF) div 100 + 1); Div100PostShift = 5; {$ELSEIF DEFINED(WIN64)} // Unchecked!! Div100Const = $A3D70A3D70A3D71; // UInt64(UInt128($3 FFFF FFFF FFFF FFFF) div 100 + 1); // UInt128 is fictive type. Div100PostShift = 2; {$IFEND} // Calculates X div 100 using multiplication by a constant,taking the // high part of the 64 bit (or 128 bit) result and shifting // right. The remainder is calculated as X - quotient * 100; // This was tested to work safely and quickly for all values of UInt32. function DivMod100(var X: NativeUInt): NativeUInt; {$IFDEF WIN32} asm // EAX = address of X,X is UInt32 here. PUSH EBX MOV EDX,Div100Const MOV ECX,EAX MOV EAX,[ECX] MOV EBX,EAX MUL EDX SHR EDX,Div100PostShift MOV [ECX],EDX // Quotient // Slightly faster than MUL LEA EDX,[EDX + 4*EDX] // EDX := EDX * 5; LEA EDX,[EDX + 4*EDX] // EDX := EDX * 5; SHL EDX,2 // EDX := EDX * 4; 5*5*4 = 100. MOV EAX,EBX SUB EAX,EDX // Remainder POP EBX end; {$ELSE WIN64} asm .NOFRAME // RCX is address of X,X is UInt64 here. MOV RAX,[RCX] MOV R8,RAX XOR RDX,RDX MOV R9,Div100Const MUL R9 SHR RDX,Div100PostShift MOV [RCX],RDX // Quotient // Faster than LEA and SHL MOV RAX,RDX MOV R9D,100 MUL R9 SUB R8,RAX MOV RAX,R8 // Remainder end; {$ENDIF WIN32} 解决方法
像往常一样,在编写优化代码时,请使用编译器输出来提示/起始点.在一般情况下,可以安全地假设它所做的任何优化都是安全的.错误的代码编译器错误很少见.
gcc使用常量0x28f5c28f5c28f5c3实现无符号64位divmod.我没有仔细研究生成除法的常数,但是有一些生成它们的算法会产生已知良好的结果(因此不需要进行详尽的测试). 代码实际上有一些重要的区别:它使用的常量与OP的常量不同. 请参阅注释以分析它实际上在做什么:首先除以4,因此它可以使用一个常数,当除数足够小时,该常数仅用于除以25.这也避免了以后需要添加. #include <stdint.h> // rem,quot ordering takes one extra instruction struct divmod { uint64_t quotient,remainder; } div_by_100(uint64_t x) { struct divmod retval = { x%100,x/100 }; return retval; } compiles to (gcc 5.3 movabs rdx,2951479051793528259 mov rax,rdi ; Function arg starts in RDI (SysV ABI) shr rax,2 mul rdx shr rdx,2 lea rax,[rdx+rdx*4] ; multiply by 5 lea rax,[rax+rax*4] ; multiply by another 5 sal rax,2 ; imul rax,rdx,100 is better here (Intel SnB). sub rdi,rax mov rax,rdi ret ; return values in rdx:rax 使用“binary”选项查看十六进制常量,因为反汇编输出就是这样做的,这与gcc的asm源输出不同. 乘以100的部分. gcc使用上面的lea / lea / shl序列,与你的问题相同.你的答案是使用mov imm / mul序列. 你的评论都说他们选择的版本更快.如果是这样,那是因为一些微妙的指令对齐或其他次要影响:在Intel SnB系列上,它是the same number of uops (3),并且相同的关键路径延迟(mov imm离开关键路径,mul是3个周期). clang uses我认为最好的选择(imul rax,100).在我看到clang选择它之前我想到了它,而不是重要的.这是1个融合域uop(只能在p0上执行),仍然具有3c延迟.因此,如果您使用此例程进行多精度延迟限制,它可能无济于事,但它是最佳选择. (如果你有延迟限制,将代码内联到循环而不是通过内存传递其中一个参数可以节省很多周期.) imul有效,因为you’re only using the low 64b of the result.mul没有2或3的操作数形式,因为无论输入的有符号或无符号解释如何,结果的低半部分都是相同的. BTW,与-march = native使用mulx为64×64-> 128而不是mul,但是没有获得任何东西.根据Agner Fog的表格,它比mul延迟一个周期. 对于imul r,r,i(特别是64b版本),AMD的延迟比3c差,这也许是gcc避免它的原因. IDK gcc维护者在调整成本方面做了多少工作,所以像-mtune = haswell这样的设置工作得很好,但是很多代码都没有用任何-mtune设置编译(即使是-march暗示的一个),所以我并不感到惊讶gcc为旧CPU或AMD提供最佳选择. clang仍然使用imul r64,r64,imm和-mtune = bdver1(Bulldozer),它可以节省m-ops但成本比使用lea / lea / shl要高1c. (标度> 1的lea是Bulldozer上的2c延迟). (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |