• gcc如何将常量除法转换为乘法及移位


    一、强度削弱
    之前在偶尔看gcc对于除以一个常数的表达式生成的汇编代码中,发现一条除法表达式生成的汇编指令非常多,这些指令中没有乘法操作,比较明显的特征就是进行了一个大整数的乘法,之后是移位啊、减法啊什么的操作,虽然不知道是什么意思,但是感觉很厉害的样子。
    后台就觉得这应该是一个优化,搜索了一下,看到gcc中的相关实现是基于一篇文章的描述实现,事实上,gcc中这部分代码的作者就是该文章的一个作者,结合gcc的代码、生成的汇编指令及这篇文章的内容惊人的一致,所以要理解这部分代码,这篇文章是基础。
    理论上的东西在计算机工程中可能会收到各种限制,例如说理论上从不用考虑整数分解的复杂度,不用考虑浮点数的精度,整数的截断、指令中操作数长度等工程性的问题,而这些则是计算机工程中需要考虑的问题。
    二、基本的想法
    基本的想法就是将除法转换为整数乘法,即首先将计算结果放大,而不重要的部分将会受到计算机指令及操作数的影响而被自动丢弃,这个“不重要部分”需要满足的基本条件就是足够小,永远到底有多远,足够小到底有多小。直观上可以这么想,对于正常的整数除法(n/d)*x=y,每当x增加d,此时的y增加1。我们可以设计一个满足这个特性的式子n*m来代替n/d,n*m也满足对于n*m*x,每当x增加d时,n*m*x增加1+delta,也就是说n*m也增加1并且多了一点点,这一点点需要足够的小,小到满足文章中给出的基础表达式:
    2^(N+l) <= m*d <= 2^(N + l) + 2^l
    这里出现的N是为了便于使用大部分体系结构中都存在的两个整数相乘,结果存放在两个整数寄存器中,例如intel体系下两个int相乘的结果放在eax和edx中,使用N这样就可以直接从寄存器中获得这个移位前的值。上面的表达式,对于任意一个-2^N到2^N之间的数,m*d右移N+l位,它和1的差距都小于1/2^N,从而保证误差在1之内,利用整数的向下取整即可获得正确的结果。
     
    还有的限制是:
    所有的操作数及中间结果不能溢出一个int的大小,这一点是一个必须小心谨慎保证的一个变量。
    这里最为复杂的表达式是当m>=2^m时,其计算方法为
    t1=MULUH(m-2^N,n)
    q=SRL(t1+SRL(n-t1,1),shpost-1)
    其实它的第二个式子就是
    SRL(t1+(n-t1)/2,shpost-1)=SRL((n+t1)/2,shpost-1)=SRL((n+t1),shpost)
    由于t1是m*n结果中由小于2^N部分和m乘法之后的高int内容,加上第二个式子中计算的m本身大于2^N的部分,就是m*n中高int中的内容,最后再右移post,即最开始表达式中的l,总共移动了N+l个bit,满足文章最开始的定理限制。
    t1+(n-t1)/2
    这个需要解释下:
    由于
    t1=MULUH(m-2^N,n) <= MULUH(2^N,n) < n
    所以n-t1>0,进而由于
    t1+(n-t1)/2=(n+t1)/2,由于2<2^N,所有(n+t1)/2能够保存在一个Nbit的int中。
    再说明下m为什么最多占用N+1个bit。在choose_multiplier函数中
    由于 m*d<2^(N+post)(1+2^(-prec))所以
    m<2^(N+post)(1+2^(-prec))/d
    由于2^(l-1) <d,所以
    m<2^(N+post)(1+2^(-prec))/d<2^(N+post)(1+2^(-prec))/2^(l-1)<2^(N+post)(1)/2^(1-l)=2^(N+post+1-l)
    加上N+post<= l+prec,所以
    m<2^(prec+1)
    当prec为无符号时,此时prec为32,m最大为33bit。
    三、有符号处理
    基本和这个类似,但是有负数的算数右移和正数右移不同,
    tsecer@harry :echo $((-5>>2)) $((5>>2))
    -2 1
    这一点可以这么理解,内码表示统一看作负数则这些数是按照
    0 1 0x7FFFFFFF 0x80000000 ……  0xFFFFFFFE(-2) 0xFFFFFFFF(-1)
    当移位时始终是向左取整,转换为负数之后就是向负无穷偏移了。为了这种情况,需要专门为这种移位进行一个处理,就是负数移位需要加上1,正数移位则不需要。
    四、测试代码例子
    tsecer@harry :cat div2mul.c 
    unsigned int uiDPos(unsigned int x)
    {
            return x / 21; 
    }
     
    int iDPos(int x) 
    {
            return x / 21;
    }
     
    int uiDNeg(unsigned int x)
    {
            return x / -21;
    }
     
    int iDNeg(int x)
    {
            return x / -21;
    }
    tsecer@harry :gcc div2mul.c -S
    tsecer@harry :cat div2mul.s 
            .file   "div2mul.c"
            .text
    .globl uiDPos
            .type   uiDPos, @function
    uiDPos:
    .LFB2:
            pushq   %rbp
    .LCFI0:
            movq    %rsp, %rbp
    .LCFI1:
            movl    %edi, -4(%rbp)
            movl    -4(%rbp), %eax
            movl    %eax, -12(%rbp)
            movl    $-2045222521, -16(%rbp)
            movl    -16(%rbp), %eax
            mull    -12(%rbp)
            movl    %edx, %ecx
            movl    -12(%rbp), %eax
            subl    %ecx, %eax
            shrl    %eax
            leal    (%rcx,%rax), %eax
            shrl    $4, %eax
            leave
            ret
    .LFE2:
            .size   uiDPos, .-uiDPos
    .globl iDPos
            .type   iDPos, @function
    iDPos:
    .LFB3:
            pushq   %rbp
    .LCFI2:
            movq    %rsp, %rbp
    .LCFI3:
            movl    %edi, -4(%rbp)
            movl    -4(%rbp), %ecx
            movl    $818089009, -12(%rbp)
            movl    -12(%rbp), %eax
            imull   %ecx
            sarl    $2, %edx
            movl    %ecx, %eax
            sarl    $31, %eax
            movl    %edx, %ecx
            subl    %eax, %ecx
            movl    %ecx, %eax
            leave
            ret
    .LFE3:
            .size   iDPos, .-iDPos
    .globl uiDNeg
            .type   uiDNeg, @function
    uiDNeg:
    .LFB4:
            pushq   %rbp
    .LCFI4:
            movq    %rsp, %rbp
    .LCFI5:
            movl    %edi, -4(%rbp)
            movl    -4(%rbp), %eax
            cmpl    $-21, %eax
            setae   %al
            movzbl  %al, %eax
            leave
            ret
    .LFE4:
            .size   uiDNeg, .-uiDNeg
    .globl iDNeg
            .type   iDNeg, @function
    iDNeg:
    .LFB5:
            pushq   %rbp
    .LCFI6:
            movq    %rsp, %rbp
    .LCFI7:
            movl    %edi, -4(%rbp)
            movl    -4(%rbp), %ecx
            movl    $818089009, -12(%rbp)
            movl    -12(%rbp), %eax
            imull   %ecx
            sarl    $2, %edx
            movl    %ecx, %eax
            sarl    $31, %eax
            subl    %edx, %eax
            leave
            ret
    .LFE5:
            .size   iDNeg, .-iDNeg
            .section        .eh_frame,"a",@progbits
    .Lframe1:
            .long   .LECIE1-.LSCIE1
    .LSCIE1:
            .long   0x0
            .byte   0x1
            .string "zR"
            .uleb128 0x1
            .sleb128 -8
            .byte   0x10
            .uleb128 0x1
            .byte   0x3
            .byte   0xc
            .uleb128 0x7
            .uleb128 0x8
            .byte   0x90
            .uleb128 0x1
            .align 8
    .LECIE1:
    .LSFDE1:
            .long   .LEFDE1-.LASFDE1
    .LASFDE1:
            .long   .LASFDE1-.Lframe1
            .long   .LFB2
            .long   .LFE2-.LFB2
            .uleb128 0x0
            .byte   0x4
            .long   .LCFI0-.LFB2
            .byte   0xe
            .uleb128 0x10
            .byte   0x86
            .uleb128 0x2
            .byte   0x4
            .long   .LCFI1-.LCFI0
            .byte   0xd
            .uleb128 0x6
            .align 8
    .LEFDE1:
    .LSFDE3:
            .long   .LEFDE3-.LASFDE3
    .LASFDE3:
            .long   .LASFDE3-.Lframe1
            .long   .LFB3
            .long   .LFE3-.LFB3
            .uleb128 0x0
            .byte   0x4
            .long   .LCFI2-.LFB3
            .byte   0xe
            .uleb128 0x10
            .byte   0x86
            .uleb128 0x2
            .byte   0x4
            .long   .LCFI3-.LCFI2
            .byte   0xd
            .uleb128 0x6
            .align 8
    .LEFDE3:
    .LSFDE5:
            .long   .LEFDE5-.LASFDE5
    .LASFDE5:
            .long   .LASFDE5-.Lframe1
            .long   .LFB4
            .long   .LFE4-.LFB4
            .uleb128 0x0
            .byte   0x4
            .long   .LCFI4-.LFB4
            .byte   0xe
            .uleb128 0x10
            .byte   0x86
            .uleb128 0x2
            .byte   0x4
            .long   .LCFI5-.LCFI4
            .byte   0xd
            .uleb128 0x6
            .align 8
    .LEFDE5:
    .LSFDE7:
            .long   .LEFDE7-.LASFDE7
    .LASFDE7:
            .long   .LASFDE7-.Lframe1
            .long   .LFB5
            .long   .LFE5-.LFB5
            .uleb128 0x0
            .byte   0x4
            .long   .LCFI6-.LFB5
            .byte   0xe
            .uleb128 0x10
            .byte   0x86
            .uleb128 0x2
            .byte   0x4
            .long   .LCFI7-.LCFI6
            .byte   0xd
            .uleb128 0x6
            .align 8
    .LEFDE7:
            .ident  "GCC: (GNU) 4.1.2 20070115 (prerelease) (SUSE Linux)"
            .section        .note.GNU-stack,"",@progbits
    tsecer@harry :
    五、gcc代码
     
    /* Choose a minimal N + 1 bit approximation to 1/D that can be used to
       replace division by D, and put the least significant N bits of the result
       in *MULTIPLIER_PTR and return the most significant bit.
     
       The width of operations is N (should be <= HOST_BITS_PER_WIDE_INT), the
       needed precision is in PRECISION (should be <= N).
     
       PRECISION should be as small as possible so this function can choose
       multiplier more freely.
     
       The rounded-up logarithm of D is placed in *lgup_ptr.  A shift count that
       is to be used for a final right shift is placed in *POST_SHIFT_PTR.
     
       Using this function, x/D will be equal to (x * m) >> (*POST_SHIFT_PTR),
       where m is the full HOST_BITS_PER_WIDE_INT + 1 bit multiplier.  */
     
    static
    unsigned HOST_WIDE_INT
    choose_multiplier (unsigned HOST_WIDE_INT d, int n, int precision,
       rtx *multiplier_ptr, int *post_shift_ptr, int *lgup_ptr)
    {
      HOST_WIDE_INT mhigh_hi, mlow_hi;
      unsigned HOST_WIDE_INT mhigh_lo, mlow_lo;
      int lgup, post_shift;
      int pow, pow2;
      unsigned HOST_WIDE_INT nl, dummy1;
      HOST_WIDE_INT nh, dummy2;
     
      /* lgup = ceil(log2(divisor)); */
      lgup = ceil_log2 (d);
     
      gcc_assert (lgup <= n);
     
      pow = n + lgup;
      pow2 = n + lgup - precision;
     
      /* We could handle this with some effort, but this case is much
         better handled directly with a scc insn, so rely on caller using
         that.  */
      gcc_assert (pow != 2 * HOST_BITS_PER_WIDE_INT);
     
      /* mlow = 2^(N + lgup)/d */
     if (pow >= HOST_BITS_PER_WIDE_INT)
        {
          nh = (HOST_WIDE_INT) 1 << (pow - HOST_BITS_PER_WIDE_INT);
          nl = 0;
        }
      else
        {
          nh = 0;
          nl = (unsigned HOST_WIDE_INT) 1 << pow;
        }
      div_and_round_double (TRUNC_DIV_EXPR, 1, nl, nh, d, (HOST_WIDE_INT) 0,
    &mlow_lo, &mlow_hi, &dummy1, &dummy2);
     
      /* mhigh = (2^(N + lgup) + 2^N + lgup - precision)/d */
      if (pow2 >= HOST_BITS_PER_WIDE_INT)
        nh |= (HOST_WIDE_INT) 1 << (pow2 - HOST_BITS_PER_WIDE_INT);
      else
        nl |= (unsigned HOST_WIDE_INT) 1 << pow2;
      div_and_round_double (TRUNC_DIV_EXPR, 1, nl, nh, d, (HOST_WIDE_INT) 0,
    &mhigh_lo, &mhigh_hi, &dummy1, &dummy2);
     
      gcc_assert (!mhigh_hi || nh - d < d);
      gcc_assert (mhigh_hi <= 1 && mlow_hi <= 1);
      /* Assert that mlow < mhigh.  */
      gcc_assert (mlow_hi < mhigh_hi
          || (mlow_hi == mhigh_hi && mlow_lo < mhigh_lo));
     
      /* If precision == N, then mlow, mhigh exceed 2^N
         (but they do not exceed 2^(N+1)).  */
     
      /* Reduce to lowest terms.  */
      for (post_shift = lgup; post_shift > 0; post_shift--)
        {
          unsigned HOST_WIDE_INT ml_lo = (mlow_hi << (HOST_BITS_PER_WIDE_INT - 1)) | (mlow_lo >> 1);
          unsigned HOST_WIDE_INT mh_lo = (mhigh_hi << (HOST_BITS_PER_WIDE_INT - 1)) | (mhigh_lo >> 1);
          if (ml_lo >= mh_lo)
    break;
     
          mlow_hi = 0;
          mlow_lo = ml_lo;
          mhigh_hi = 0;
          mhigh_lo = mh_lo;
        }
     
      *post_shift_ptr = post_shift;
      *lgup_ptr = lgup;
      if (n < HOST_BITS_PER_WIDE_INT)
        {
          unsigned HOST_WIDE_INT mask = ((unsigned HOST_WIDE_INT) 1 << n) - 1;
          *multiplier_ptr = GEN_INT (mhigh_lo & mask);
          return mhigh_lo >= mask;
        }
      else
        {
          *multiplier_ptr = GEN_INT (mhigh_lo);
          return mhigh_hi;
        }
    }
  • 相关阅读:
    java提高篇(九)-----实现多重继承
    java提高篇(八)----详解内部类
    java提高篇(七)-----关键字static
    在tomcat下部署工程
    java提高篇(六)-----使用序列化实现对象的拷贝
    java提高篇(五)-----抽象类与接口
    java提高篇(四)-----理解java的三大特性之多态
    java提高篇(三)-----java的四舍五入
    java那些小事---用偶数做判断,不要用基数做判断
    java提高篇(二)-----理解java的三大特性之继承
  • 原文地址:https://www.cnblogs.com/tsecer/p/10487623.html
Copyright © 2020-2023  润新知