由运行时常量值重复整数除

时间:2017-07-27 14:23:56

标签: c++ assembly optimization x86-64

在我的程序中的某个时刻,我计算整数除数d。从那时起,d将保持不变。

稍后在代码中,我将多次除以d - 执行整数除法,因为d的值不是编译时已知的常量。

鉴于与其他类型的整数运算相比,整数除法是一个相对较慢的过程,我想优化它。是否有一些我可以存储d的替代格式,以便分割过程执行得更快?也许是某种形式的倒数?

我不需要其他任何内容的d值。

d的值是任何64位整数,但通常很适合32位。

3 个答案:

答案 0 :(得分:30)

有一个图书馆 - libdivide

  

libdivide是一个用于优化整数除法的开源库

     

libdivide允许您用替换昂贵的整数除法   相对便宜的乘法和位移。编译器通常会这样做   这,但只有在编译时才知道除数。 libdivide   允许您在运行时利用它。结果是   整数除法可以变得更快 - 速度更快。此外,   libdivide允许您将SSE2向量除以运行时常量,   这特别好,因为SSE2没有整数除法   指令!

     

libdivide是免费的开源许可证。名字   " libdivide"有点儿开玩笑,因为本身没有图书馆:   代码完全打包为单个头文件,包含C和a   C ++ API。

您可以在此blog了解其背后的算法;例如,这entry

基本上,它背后的算法与编译器用于通过常量优化除法的算法相同,除了它允许在运行时完成这些强度降低优化。

注意:您可以创建更快的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