对于我的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}
答案 0 :(得分:2)
与编写优化代码时一样,使用编译器输出提示/起始点。在一般情况下,假设它所做的任何优化是安全的,这是安全的。错误的代码编译器错误很少见。
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 -O3 -mtune=haswell
):
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
使用&#34;二进制&#34;选项以十六进制的形式查看常量,因为反汇编输出就是这样做的,这与gcc的asm源输出不同。
gcc使用上面的lea / lea / shl序列,与你的问题相同。您的答案是使用mov imm
/ mul
序列。
你的评论都说他们选择的版本更快。如果是这样,那是因为一些微妙的指令对齐或其他次要影响:在Intel SnB系列上,它是the same number of uops (3),并且相同的关键路径延迟(mov imm
是离开关键路径,mul
是3个周期。
clang uses我认为最好的选择(imul rax, rdx, 100
)。在我看到clang选择它之前我想到了它,而不是重要的。这是1个融合域uop(只能在p0上执行),仍有3c延迟。因此,如果您使用此例程进行多精度延迟限制,它可能不会有帮助,但它是最佳选择。 (如果你有延迟限制,将代码内联到循环而不是通过内存传递其中一个参数可以节省很多周期。)
imul
有效,因为you're only using the low 64b of the result。 mul
没有2或3的操作数形式,因为无论输入的有符号或无符号解释如何,结果的低半部分都是相同的。
顺便说一句,与-march=native
一起使用mulx
使用mul
表示64x64-> 128,而不是mul
,但它并没有获得任何收益。根据Agner Fog的表格,它的延迟比imul r,r,i
差一个周期。
AMD -mtune=haswell
(特别是64b版本)的延迟时间比3c差,这也许是gcc避免它的原因。 IDK gcc维护者在调整成本方面的工作量是多少,因此像-mtune
这样的设置运行良好,但很多的代码没有使用任何-march
设置编译(甚至一个由imul r64, r64, imm
暗示,所以当gcc做出最适合旧CPU或AMD的选择时,我并不感到惊讶。
clang仍然使用-mtune=bdver1
和{{1}}(Bulldozer),这可以节省m-ops,但是比使用lea / lea / shl要花费1c的延迟。 (规模&gt; 1的lea在Bulldozer上是2c延迟)。
答案 1 :(得分:1)
我找到了libdivide.h的解决方案。这是Win64稍微复杂的部分:
example_with_joins = CycleDay.joins(:user).where(users: { role: 'admin' })
答案 2 :(得分:1)
@ Rudy答案中的代码来自以下步骤:
0.000000(10100011110101110000)
; S = 6
; 1010 0011 1101 0111 0000 1010 0011 1101 0111 0000 1010 0011 1101 0111 0000 1010 0011 1101
1010 0011 1101 0111 0000 1010 0011 1101 0111 0000 1010 0011 1101 0111 0000 1010 1
1
位: 0100 0111 1010 1110 0001 0100 0111 1010 1110 0001 0100 0111 1010 1110 0001 0101
A = 47 AE 14 7A E1 47 AE 15
X div 100 = (((uint128(X) * uint128(A)) shr 64) + X) shr 7
(7 = 1 + S)