在我的程序中的某个时刻,我计算整数除数d
。从那时起,d
将保持不变。
稍后在代码中,我将多次除以d
- 执行整数除法,因为d
的值不是编译时已知的常量。
鉴于与其他类型的整数运算相比,整数除法是一个相对较慢的过程,我想优化它。是否有一些我可以存储d
的替代格式,以便分割过程执行得更快?也许是某种形式的倒数?
我不需要其他任何内容的d
值。
d
的值是任何64位整数,但通常很适合32位。
答案 0 :(得分:30)
有一个图书馆 - libdivide:
libdivide是一个用于优化整数除法的开源库
libdivide允许您用替换昂贵的整数除法 相对便宜的乘法和位移。编译器通常会这样做 这,但只有在编译时才知道除数。 libdivide 允许您在运行时利用它。结果是 整数除法可以变得更快 - 速度更快。此外, libdivide允许您将SSE2向量除以运行时常量, 这特别好,因为SSE2没有整数除法 指令!
libdivide是免费的开源许可证。名字 " libdivide"有点儿开玩笑,因为本身没有图书馆: 代码完全打包为单个头文件,包含C和a C ++ API。
基本上,它背后的算法与编译器用于通过常量优化除法的算法相同,除了它允许在运行时完成这些强度降低优化。
注意:您可以创建更快的libdivide版本。我们的想法是,对于每个除数,您始终可以创建三元组(mul
/ add
/ shift
),因此此表达式会给出结果:(num
* {{ 1}} + mul
)>> add
(乘法是这里的宽乘法)。有趣的是,这种方法可以胜过编译器版本,用于几个微基准测试的恒定除法!
这是我的实现(这不是开箱即用的可编译,但可以看到一般算法):
shift
答案 1 :(得分:6)
“Hacker's delight”这本书有“第10章:整数除以常数”,共74页。您可以在此目录中免费找到所有代码示例: http://www.hackersdelight.org/hdcode.htm 在你的情况下,图。 10-1。,10-2和10-3是你想要的。
除以常数 d 的问题相当于多次使用 c = 1 / d 。这些算法为您计算这样的常数。一旦你有 c ,你就可以计算股息:
int divideByMyConstant(int dividend){
int c = MAGIC; // Given by the algorithm
// since 1/d < 1, c is actually (1<<k)/d to fit nicely ina 32 bit int
int k = MAGIC_SHIFT; //Also given by the algorithm
long long tmp = (long long)dividend * c; // use 64 bit to hold all the precision...
tmp >>= k; // Manual floating point number =)
return (int)tmp;
}
答案 2 :(得分:2)
update - 在我的原始答案中,我注意到在先前线程中提到的算法,用于编译器生成的除以常数的代码。编写汇编代码以匹配从该先前线程链接到的文档。编译器生成的代码涉及稍微不同的序列,具体取决于除数。
在这种情况下,除非在运行时才知道除数,因此需要一种通用的算法。 geza的答案中的示例显示了一个通用算法,可以使用GCC在汇编代码中内联,但Visual Studio不支持64位模式的内联汇编。在Visual Studio的情况下,如果使用内在函数而不是调用用汇编编写的函数,则需要在所涉及的额外代码之间进行权衡。在我的系统上(英特尔3770k 3.5ghz)我尝试调用一个函数来添加adc shr |,我也尝试使用指针函数来选择使用更短的序列| mul shr |或者| shr(1)mul shr |取决于除数,但这提供很少或没有增益,具体取决于编译器。这种情况下的主要开销是调用(与| mul add adc shr |)。即使有了调用开销,序列调用也会添加adc shr ret |平均速度是我系统上除法的4倍。
请注意,在geza的答案中链接到libdivide的源代码没有可以处理除数的公共例程== 1. libdivide公共序列是乘法,减法,shift(1),add,shift,与geza的比较例子c ++ multiply,add,adc,shift的序列。
我的原始答案:下面的示例代码使用先前线程中描述的算法。
Why does GCC use multiplication by a strange number in implementing integer division?
这是另一个帖子中提到的文件的链接:
http://gmplib.org/~tege/divcnst-pldi94.pdf
下面的示例代码基于pdf文档,适用于Visual Studio,使用ml64(64位汇编程序),在Windows(64位操作系统)上运行。标签main ...和dcm ...的代码是生成preshift(rspre,除数中的尾随零位数),乘数和postshift(rspost)的代码。标签为dct的代码是测试方法的代码。
includelib msvcrtd
includelib oldnames
sw equ 8 ;size of word
.data
arrd dq 1 ;array of test divisors
dq 2
dq 3
dq 4
dq 5
dq 7
dq 256
dq 3*256
dq 7*256
dq 67116375
dq 07fffffffffffffffh ;max divisor
dq 0
.data?
.code
PUBLIC main
main PROC
push rbp
push rdi
push rsi
sub rsp,64 ;allocate stack space
mov rbp,rsp
lea rsi,arrd ;set ptr to array of divisors
mov [rbp+6*sw],rsi
jmp main1
main0: mov [rbp+0*sw],rbx ;[rbp+0*sw] = rbx = divisor = d
cmp rbx,1 ;if d <= 1, q=n or overflow
jbe main1
bsf rcx,rbx ;rcx = rspre
mov [rbp+1*sw],rcx ;[rbp+1*sw] = rspre
shr rbx,cl ;rbx = d>>rsc
bsr rcx,rbx ;rcx = floor(log2(rbx))
mov rsi,1 ;rsi = 2^floor(log2(rbx))
shl rsi,cl
cmp rsi,rbx ;br if power of 2
je dcm03
inc rcx ;rcx = ceil(log2(rcx)) = L = rspost
shl rsi,1 ;rsi = 2^L
; jz main1 ;d > 08000000000000000h, use compare
add rcx,[rbp+1*sw] ;rcx = L+rspre
cmp rcx,8*sw ;if d > 08000000000000000h, use compare
jae main1
mov rax,1 ;[rbp+3*sw] = 2^(L+rspre)
shl rax,cl
mov [rbp+3*sw],rax
sub rcx,[rbp+1*sw] ;rcx = L
xor rdx,rdx
mov rax,rsi ;hi N bits of 2^(N+L)
div rbx ;rax == 1
xor rax,rax ;lo N bits of 2^(N+L)
div rbx
mov rdi,rax ;rdi = mlo % 2^N
xor rdx,rdx
mov rax,rsi ;hi N bits of 2^(N+L) + 2^(L+rspre)
div rbx ;rax == 1
mov rax,[rbp+3*sw] ;lo N bits of 2^(N+L) + 2^(L+rspre)
div rbx ;rax = mhi % 2^N
mov rdx,rdi ;rdx = mlo % 2^N
mov rbx,8*sw ;rbx = e = # bits in word
dcm00: mov rsi,rdx ;rsi = mlo/2
shr rsi,1
mov rdi,rax ;rdi = mhi/2
shr rdi,1
cmp rsi,rdi ;break if mlo >= mhi
jae short dcm01
mov rdx,rsi ;rdx = mlo/2
mov rax,rdi ;rax = mhi/2
dec rbx ;e -= 1
loop dcm00 ;loop if --shpost != 0
dcm01: mov [rbp+2*sw],rcx ;[rbp+2*sw] = shpost
cmp rbx,8*sw ;br if N+1 bit multiplier
je short dcm02
xor rdx,rdx
mov rdi,1 ;rax = m = mhi + 2^e
mov rcx,rbx
shl rdi,cl
or rax,rdi
jmp short dct00
dcm02: mov rdx,1 ;rdx = 2^N
dec qword ptr [rbp+2*sw] ;dec rspost
jmp short dct00
dcm03: mov rcx,[rbp+1*sw] ;rcx = rsc
jmp short dct10
; test rbx = n, rdx = m bit N, rax = m%(2^N)
; [rbp+1*sw] = rspre, [rbp+2*sw] = rspost
dct00: mov rdi,rdx ;rdi:rsi = m
mov rsi,rax
mov rbx,0fffffffff0000000h ;[rbp+5*sw] = rbx = n
dct01: mov [rbp+5*sw],rbx
mov rdx,rdi ;rdx:rax = m
mov rax,rsi
mov rcx,[rbp+1*sw] ;rbx = n>>rspre
shr rbx,cl
or rdx,rdx ;br if 65 bit m
jnz short dct02
mul rbx ;rdx = (n*m)>>N
jmp short dct03
dct02: mul rbx
sub rbx,rdx
shr rbx,1
add rdx,rbx
dct03: mov rcx,[rbp+2*sw] ;rcx = rspost
shr rdx,cl ;rdx = q = quotient
mov [rbp+4*sw],rdx ;[rbp+4*sw] = q
xor rdx,rdx ;rdx:rax = n
mov rax,[rbp+5*sw]
mov rbx,[rbp+0*sw] ;rbx = d
div rbx ;rax = n/d
mov rdx,[rbp+4*sw] ;br if ok
cmp rax,rdx ;br if ok
je short dct04
nop ;debug check
dct04: mov rbx,[rbp+5*sw]
inc rbx
jnz short dct01
jmp short main1
; test rbx = n, rcx = rsc
dct10: mov rbx,0fffffffff0000000h ;rbx = n
dct11: mov rsi,rbx ;rsi = n
shr rsi,cl ;rsi = n>>rsc
xor edx,edx
mov rax,rbx
mov rdi,[rbp+0*sw] ;rdi = d
div rdi
cmp rax,rsi ;br if ok
je short dct12
nop
dct12: inc rbx
jnz short dct11
main1: mov rsi,[rbp+6*sw] ;rsi ptr to divisor
mov rbx,[rsi] ;rbx = divisor = d
add rsi,1*sw ;advance ptr
mov [rbp+6*sw],rsi
or rbx,rbx
jnz main0 ;br if not end table
add rsp,64 ;restore regs
pop rsi
pop rdi
pop rbp
xor rax,rax
ret 0
main ENDP
END