找回密码
 欢迎注册
查看: 6351|回复: 9

[分享] Exact division 严格整除法

[复制链接]
发表于 2016-11-25 15:48:43 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?欢迎注册

×
这里,我介绍一种除法算法,Exact division,可翻译成严格整除法,这个帖子不但给出算法,而且给出具体的代码和优化过程及结论。

在GMP的文档中,提到一些函数名包含divexact的除法函数,如mpz_divexact_ui。见下。

void mpz_divexact_ui (mpz t q, const mpz t n, unsigned long d)
Set q to n/d. These functions produce correct results only when it is known in advance that d divides n.
These routines are much faster than the other division functions, and are the best choice when exact division is known to occur, for example reducing a rational to lowest terms.
翻译成中文
void mpz_divexact_ui (mpz t q, const mpz t n, unsigned long d)
计算n/d商并赋值给q。当事先已经知道除数d能够整除被除数n(被除数是除数的倍数),在这种情况下,这个函数能够得到正确的结果。这些例程比其他除法函数更快,当知道除数d能够整除被除数n时,使用这个函数是最佳的选择。
GMP文档中也给出了算法的描述,见下
15.2.5 Exact Division
A so-called exact division is when the dividend is known to be an exact multiple of the divisor.
Jebelean’s exact division algorithm uses this knowledge to make some significant optimizations
(see Appendix B [References], page 126).
The idea can be illustrated in decimal for example with 368154 divided by 543. Because the low digit of the dividend is 4, the low digit of the quotient must be 8. This is arrived at from 4X7 mod 10, using the fact 7 is the modular inverse of 3 (the low digit of the divisor), since 3X7= 1 mod 10. So 8X543 = 4344 can be subtracted from the dividend leaving 363810. Notice the low digit has become zero. The procedure is repeated at the second digit, with the next quotient digit 7 (1X7 mod 10), subtracting 7X543 = 3801, leaving 325800. And finally at the third digit with quotient digit 6 (8X7 mod 10), subtracting 6X543 = 3258 leaving 0. So the quotient is 678.
翻译成中文
15.2.5整除
一个所谓的“严格除法”是这样的,当被除数是除数的倍数时,jebelean的”严格除法“使用数论方面的知识,做一些重要的优化(见附录B [参考文献],126页)。
这个算法,可以用十进制的例子来说明,例如368154除以543。因为被除数低位的数字为4,低位商必须为8,这是根据4X7 mod 10等于8这个方法来的。7是3(被除数的最低位)的模逆(数论中的逆元),因为3X7=1 mod 10。8X543 = 4344,从被除数中减去4344,留下余数363810。注意最低位数字已变成零。重复这个过程在倒数第2位数字,可以得到下一位商7(1x7 mod 10), 7x543 = 3801,余下325800。最后,得到第3位商6(8X7 mod 10),减去6x543 = 3258余下0。所以这个算式的商是678。
   一点说明,正如,减去一个数等于加上这个数的补数一样,除以一个数等于乘以这个数的倒数,这里我们又发现,乘以一个数的逆元可以得到商。补数,倒数,逆元这三个概念很类似,但是又不尽相同。当进制为10时,1是9的补数,9同样是1的补数。同理,3和7互为补数。逆元是数论中模算术的概念,不是所有的数都有逆元。仅当一个数a和模m互素时,数a才有逆元。当m=10时,10以下的数字中,仅1,3,7,9这四个数有逆元。1的逆元是1, 9的逆元仍然是9,3和7互为逆元。如果你感兴趣,可从网上或者数论方面的书得到更多模算术的知识,这里不再重复。

Jebelean 方法和一般方法除法相比,最重要的区别是
1.  从低位开始,先得到商的最低位,然后得到次低位。最后得到商的最高位。而通常的除法则是从高位开始试商。
2.  试商时采用模算术,而不是普通的算术。理论上,这个算法可应用到基为2^B进制的系统,也可以应用到基为10^B进制的系统,但是在基为10^B进制系统中,模算术非常慢,所以没有实用价值。而在基为2^B的进制中,除以2^B的模可使用“and”指令来得到,速度非常快,即 a mod (2^B)= a and (2^B-1)。


毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2016-11-25 15:55:32 | 显示全部楼层
我学习了下GMP的代码。完成了基为2^30 和GMP类似的除法函数。

这个算法的第一步是求n的逆元。下面函数mod_inverse,使用欧几里得辗转相除法,可得到a关于任意m的逆元。

  1. int extgcd(DWORD a, DWORD b, DWORD *px, DWORD *py)  
  2. {  
  3.         DWORD d = a;  
  4.     if(b != 0)
  5.         {  
  6.         d = extgcd(b, a % b, py, px);  
  7.                 *py -= (a / b) * (*px);  
  8.     }
  9.         else
  10.         {  
  11.         *px = 1;  
  12.         *py = 0;  
  13.     }  
  14.     return d;  
  15. }  

  16. int mod_inverse(DWORD a, DWORD m)  
  17. {  
  18.         DWORD x,y;
  19.     extgcd(a, m, &x, &y);  
  20.         return ( (m + x) % m);  
  21. }  
复制代码


在GMP的代码中,则是使用一次查表,再加上2次迭代来快速地求逆元。值得注意是,求逆元的迭代算法和求倒数的迭代算法非常类似。下面给出求模=2^30的逆元,下面的代码中,MASK_B20= (2^30-1)= 0x3fffffff
  1. /* g_bi_tab[i] is the multiplicative inverse of 2*i+1 mod 256,
  2.    ie. (g_bi_tab[i] * (2*i+1)) % 256 == 1 */

  3. const BYTE g_bi_tab[128] =
  4. {
  5.   0x01, 0xAB, 0xCD, 0xB7, 0x39, 0xA3, 0xC5, 0xEF,
  6.   0xF1, 0x1B, 0x3D, 0xA7, 0x29, 0x13, 0x35, 0xDF,
  7.   0xE1, 0x8B, 0xAD, 0x97, 0x19, 0x83, 0xA5, 0xCF,
  8.   0xD1, 0xFB, 0x1D, 0x87, 0x09, 0xF3, 0x15, 0xBF,
  9.   0xC1, 0x6B, 0x8D, 0x77, 0xF9, 0x63, 0x85, 0xAF,
  10.   0xB1, 0xDB, 0xFD, 0x67, 0xE9, 0xD3, 0xF5, 0x9F,
  11.   0xA1, 0x4B, 0x6D, 0x57, 0xD9, 0x43, 0x65, 0x8F,
  12.   0x91, 0xBB, 0xDD, 0x47, 0xC9, 0xB3, 0xD5, 0x7F,
  13.   0x81, 0x2B, 0x4D, 0x37, 0xB9, 0x23, 0x45, 0x6F,
  14.   0x71, 0x9B, 0xBD, 0x27, 0xA9, 0x93, 0xB5, 0x5F,
  15.   0x61, 0x0B, 0x2D, 0x17, 0x99, 0x03, 0x25, 0x4F,
  16.   0x51, 0x7B, 0x9D, 0x07, 0x89, 0x73, 0x95, 0x3F,
  17.   0x41, 0xEB, 0x0D, 0xF7, 0x79, 0xE3, 0x05, 0x2F,
  18.   0x31, 0x5B, 0x7D, 0xE7, 0x69, 0x53, 0x75, 0x1F,
  19.   0x21, 0xCB, 0xED, 0xD7, 0x59, 0xC3, 0xE5, 0x0F,
  20.   0x11, 0x3B, 0x5D, 0xC7, 0x49, 0x33, 0x55, 0xFF
  21. };


  22. DWORD _b_invert_C( DWORD *pN,int* pShift)
  23. {
  24.         DWORD  inv;
  25.         int _shift;
  26.         DWORD n=*pN;

  27.         assert (n != 0);
  28.         _shift=0;
  29.         while ( (n & 1)==0 )
  30.         {
  31.                 n>>=1;        _shift++;
  32.         }

  33.         *pShift=_shift;
  34.         *pN=n;
  35.         if ( n==1)
  36.         {
  37.                 return 1;
  38.         }

  39.         inv = g_bi_tab[(n/2) & 0x7F];
  40.         inv = 2 * inv - inv * inv * n;
  41.         inv = 2 * inv - inv * inv * n;
  42.        
  43.         return  (inv & MASK_B30);
  44. }
复制代码


我还写了一个功能等价的汇编语言版本
  1. ;_declspec(naked) DWORD _b_invert_ALU( DWORD *pN, int *pShift)
  2. __b_invert_ALU PROC

  3. PARAM_np                = 4
  4. PARAM_pShift        = 8
  5. REG_rp                EQU        edi
  6. REG_np                EQU        ebx
  7. REG_shift        EQU ecx

  8.                 push REG_np
  9.                 push REG_rp

  10.                 ;--------------------------------------------
  11.                 mov  REG_np, [esp+8+PARAM_np]
  12.                 mov  REG_rp,[esp+8+PARAM_pShift]

  13.                 mov eax, DP [REG_np]
  14.                 or  eax,  eax
  15.                 jz  invalid_para

  16.                 bsf  REG_shift,eax                                ; check tail bit0, n= x * 2^ ecx
  17.                 mov  DP [REG_rp], REG_shift                ; store shift

  18.                 shr  eax,cl                                                ; n= (n >> shift)
  19.                 mov  DP [REG_np],eax                        ; update n

  20.                 cmp  eax,1
  21.                 jz   funExit                                        ; if ( n==1) return 1       

  22.                 shr    eax,1                                        ; eax= n/2
  23.                 and    eax, 127

  24.                 movzx  ecx, byte ptr _g_bi_tab[eax]                ;inv= g_bi_tab[n/2]       
  25.                
  26.                 ;  Do "inv = 2 * inv - inv * inv * n"
  27.                 mov   eax,ecx                                        ; eax=inv
  28.                 mul          ecx                                                ; edx:eax = inv*inv
  29.                 mul   DP [REG_np]                                ; edx:eax = inv*inv*n
  30.                 shl   ecx,1                                                ; inv=inv*2
  31.                 sub   ecx, eax                                        ; inv=inv*2-inv*inv*n       
  32.                
  33.                 ; Do "inv = 2 * inv - inv * inv * n" again
  34.                 mov   eax,ecx                                        ; eax=inv
  35.                 mul          ecx                                                ; edx:eax = inv*inv
  36.                 mul   DP [REG_np]                                ; edx:eax = inv*inv*n
  37.                 shl   ecx,1                                                ; inv=inv*2
  38.                 sub   ecx, eax                                        ; inv=inv*2-inv*inv*n       

  39.                 mov   eax,ecx
  40.                 and   eax,MASK_B30
  41. funExit:
  42.                 ;--------------------------------------------
  43.                 pop REG_rp
  44.                 pop REG_np
  45.                 ret

  46. invalid_para:
  47.                 mov eax,-1
  48.                 mov DP [REG_rp],eax
  49.                 pop REG_rp
  50.                 pop REG_np
  51.                 ret
  52. __b_invert_ALU ENDP

复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2016-11-25 15:58:41 | 显示全部楼层
下面是主函数,可求一个长为len*30 bit的数除以d的商,当d是2的方幂时,调用 _mpn_B30_divrem_1_C 来实现,当d是2的方幂时,函数 _mpn_B30_divrem_1_C使用速度更快的移位算法来实现,否则使用Tudor Jebelean 的新方法,调用_mpn_B30_exact_div1_core_C 来实现。
  1. // Exact Division,
  2. // Reference Tudor Jebelean, <<An algorithm for exact division>>
  3. DWORD mpn_B30_divexact_1_C (DWORD *rp, const DWORD* up, DWORD len, DWORD d)
  4. {
  5.         DWORD di;
  6.         int shift;

  7.         assert (len >= 1);
  8.         assert (d != 0);

  9.         di= _b_invert_C( &d,&shift);
  10.         if ( di==1)
  11.                 return  _mpn_B30_divrem_1_C (rp,0,up,len,d,1);
  12.         else
  13.                 return _mpn_B30_exact_div1_core_C (rp, up, len, d, di, shift);
  14. }


  15. // Exact Division,
  16. DWORD _mpn_B30_exact_div1_core_C (DWORD *rp, const DWORD *up, DWORD n, DWORD d,  DWORD di, int shift)
  17. {
  18.         DWORD  i;
  19.         DWORD  c, l, u, u_next;
  20.         UINT64 prod;

  21.         assert (n >= 1);
  22.         assert (d != 0);

  23.         if (shift != 0)
  24.         {
  25.                 c = 0;
  26.                 u = up[0];
  27.                 rp--;
  28.                 for (i = 1; i < n; i++)
  29.                 {
  30.                         u_next = up[i];
  31.                         u = ((u >> shift) | (u_next << (30 -shift))) & MASK_B30;
  32.                         l = u - c;
  33.                         c = (l > u);        // if u<c, set c=1 else c=0
  34.                           l = (l * di) & MASK_B30;
  35.                         rp[i] = l;

  36.                         prod=(UINT64)(l)* (UINT64)(d);
  37.                         c += (DWORD)(prod>>30);
  38.                         u = u_next;
  39.                 }

  40.                 u = (u >> shift);
  41.                 l = u - c;
  42.                 l = (l * di) & MASK_B30;
  43.                 rp[i] = l;
  44.         }
  45.         else
  46.         {
  47.                 u = up[0];
  48.                 l = (u * di) & MASK_B30;
  49.                 rp[0] = l;
  50.                 c = 0;
  51.                 for (i = 1; i < n; i++)
  52.                 {
  53.                         prod=(UINT64)(l)* (UINT64)(d);
  54.                         c += (DWORD)(prod>>30);
  55.                         u = up[i];
  56.                         l = u - c;
  57.                         c = (l > u);  // if u<c, set c=1 else c=0
  58.                         l = (l * di) & MASK_B30;
  59.                         rp[i] = l;
  60.                 }
  61.         }
  62.         return c;
  63. }
复制代码


作为一个追求运算速度的coder,速度达不到最快,是不会罢手的,于是又完成了使用ALU指令的汇编版本和使用XMM寄存器SSE2。下面是汇编代码,函数后缀ALU表示这个版本仅仅使用通用寄存器。

说明,当除数是奇数是(shift==0),其执行路径上的主要部分是包含一个12条指令循环,见下面的 shift0_body_start后面的指令。

  1. ;  Exact Division,
  2. ; _declspec(naked)  DWORD _mpn_B30_exact_div1_core_ALU (DWORD *rp, const DWORD *sp, DWORD n, DWORD d,  DWORD di, int shift)

  3. __mpn_B30_exact_div1_core_ALU PROC

  4. PARAM_rp    = 4
  5. PARAM_sp    = 8
  6. PARAM_len        = 12
  7. PARAM_d                = 16
  8. PARAM_di        = 20
  9. PARAM_shift = 24

  10. REG_I                EQU ebx                                ; loop varible, the initial value is -len
  11. REG_carry        EQU ebp
  12. REG_sp                EQU esi
  13. REG_rp                EQU edi
  14. REG_shift        EQU ecx

  15. OFS_SHIFT2        = 0
  16. IF_SIZE                = 20
  17.        
  18.         push esi                                                ; save registers
  19.         push edi
  20.         push ebx
  21.         push ebp
  22.         sub  esp,4

  23.         mov  REG_rp,     [esp+IF_SIZE+PARAM_rp]
  24.         mov  REG_sp,     [esp+IF_SIZE+PARAM_sp]
  25.         mov  eax,        [esp+IF_SIZE+PARAM_len]
  26.        
  27.         xor  REG_carry,REG_carry
  28.         or   eax,eax                                        ; 0 length,diretly return
  29.         jz   FunExit1

  30.         mov  REG_shift, DP [esp+IF_SIZE+PARAM_shift]
  31.         mov  edx,30
  32.         sub  edx,REG_shift
  33.         mov  [esp+OFS_SHIFT2], edx                ; esp[OFS_SHIFT2]= 30 -shift

  34.         mov  REG_I,  [esp+IF_SIZE+PARAM_len]
  35.         lea  REG_sp, [REG_sp+REG_I*4]
  36.         lea  REG_rp, [REG_rp+REG_I*4]
  37.         neg         REG_I

  38.         or   REG_shift, REG_shift
  39.         jnz  shift_not_0

  40. shift_is_0:
  41.         mov eax, [REG_sp+REG_I*4]                ; u = up[0];
  42.         mul DP [esp+IF_SIZE+PARAM_di]        ; l = (u * di)
  43.         and eax, MASK_B30                                ; l = (u * di) & MASK_B30;
  44.         mov DP [REG_rp+REG_I*4],eax                ; rp[0]=l
  45.         xor REG_carry,REG_carry                        ; c=0
  46.         add REG_I,1
  47.         jz FunExit1

  48. shift0_body_start:
  49.         mul DP [esp+IF_SIZE+PARAM_d]        ; edx: eax =(l)* (d);        
  50.         shrd eax,edx,30                                        ; eax= (prod((l)* (d))>>30;
  51.         add  REG_carry, eax                                ; c += (prod>>30);
  52.         mov  eax,DP [REG_sp+REG_I*4]        ; u = up[i];
  53.         sub  eax, REG_carry                                ; l = u - c;
  54.         sbb  REG_carry,REG_carry
  55.         imul eax, DP [esp+IF_SIZE+PARAM_di]        ; ecx= l = (l * di)
  56.         neg  REG_carry                                        ; if u<c, set c=1 else c=0        
  57.         and  eax, MASK_B30                                ; ecx= l = (l * di) & MASK_B30;
  58.         mov  DP [REG_rp+REG_I*4],eax        ; rp[i] =eax= l;
  59.         add  REG_I,1
  60.         jnz  shift0_body_start

  61. FunExit1:
  62.         mov  eax, REG_carry
  63.         add  esp,4
  64.         pop  ebp                                                ; restore registers
  65.         pop  ebx
  66.         pop  edi
  67.         pop  esi
  68.         ret

  69. shift_not_0:
  70.         xor REG_carry,REG_carry                        ; c=0
  71.         mov eax, DP [REG_sp+REG_I*4]        ; u = up[0];
  72.         add REG_I, 1                                        ; i++
  73.         jz  tail_handle

  74. shift_n0_body_start:
  75.         mov  ecx, DP [esp+OFS_SHIFT2]
  76.         mov  edx, DP [REG_sp+REG_I*4]        ; edx=u_next
  77.         shl  edx,cl                                                ; edx=u_next<< (30-shift)
  78.         mov  ecx, [esp+IF_SIZE+PARAM_shift]
  79.         shr  eax, cl                                        ; eax=(u >>shift)
  80.         or   eax, edx
  81.         and  eax, MASK_B30                                ; u = ((u >> shift) | (u_next << (30 -shift))) & MASK_B30;
  82.         sub  eax, REG_carry                                ; l= u-c
  83.         sbb  REG_carry,REG_carry               
  84.         imul eax, DP [esp+IF_SIZE+PARAM_di]        ; edx:eax= l=l*di
  85.         neg  REG_carry                                        ; if u<c, set c=1 else c=0        
  86.         and  eax, MASK_B30                                ; eax= l = (l * di) & MASK_B30;
  87.         mov  DP [REG_rp+REG_I*4-4],eax        ; eax=rp[i-1] = l;
  88.         mul  DP [esp+IF_SIZE+PARAM_d]   ; edx:eax=prod=(l)* (d);
  89.         shrd eax,edx,30                                        ; eax=(prod>>30)
  90.         add  REG_carry, eax                                ; c += (prod>>30);
  91.         mov  eax, DP [REG_sp+REG_I*4]        ; eax =u_next= [REG_sp]
  92.         add  REG_I,1
  93.         jnz shift_n0_body_start

  94. tail_handle:
  95.         mov ecx,  [esp+IF_SIZE+PARAM_shift]
  96.         shr eax, cl                                                        ; eax= u= u>>shift
  97.         sub eax, REG_carry                                        ; eax=l=u-c
  98.         mul DP DP [esp+IF_SIZE+PARAM_di]        ; eax=l= l*di
  99.         and eax, MASK_B30                                        ; eax=l= (l * di) & MASK_B30;
  100.         mov DP [REG_rp+REG_I*4-4], eax                ; rp[i]=l

  101.         mov eax, REG_carry                                        ; return c

  102. funExit2:
  103.         add  esp,4
  104.         pop  ebp                                                ; restore registers
  105.         pop  ebx
  106.         pop  edi
  107.         pop  esi
  108.         ret
  109. __mpn_B30_exact_div1_core_ALU ENDP
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2016-11-25 16:17:44 | 显示全部楼层
  1. divisor=odd number
  2.         blk_len mpz_divexact_ui mpn_B30_divexact_1_C mpn_B30_divexact_1_ALU mpn_B30_divexact_1_SSE2
  3.         8192    7.017   18.863  9.810   10.234
  4.         16384   5.697   15.315  9.808   10.229
  5.         32768   5.697   15.315  9.808   10.231
  6.         65536   5.701   15.314  9.806   10.235

  7. divisor=even number
  8.         blk_len mpz_divexact_ui mpn_B30_divexact_1_C mpn_B30_divexact_1_ALU mpn_B30_divexact_1_SSE2
  9.         8192    5.979   15.192  9.434   10.722
  10.         16384   5.968   15.106  9.433   10.716
  11.         32768   5.981   15.104  9.437   10.713
  12.         65536   5.973   15.256  9.452   10.716
复制代码


我的测试程序分别测试块长度为8K到64K是的乘法,即被除数为(blk_len*30)bit,并和GMP 32位版本的速度做了对比。因为GMP采用2^32次为基,所有其实际循环次数我的程序少1/16。但为了公平起见,blk_len仍然以30bit 为单位比较。上表给出的数据为平均每30bit的实际用时,单位为时钟周期。 测试结果显示
1. ALU版本的速度快于C语言版本,优化没有白费
2. GMP版本的函数大幅领先我的程序,GMP版仅为5.7个周期,而我的ALU版为9.8个周期。
3. 当除数为偶数时,其执行路径上的循环块为21条指令,但是执行时间比只有12条指令的奇数分支还有少。
4. 对于这个程序,SSE2版本没有优势。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2016-11-25 16:26:17 | 显示全部楼层
    为了挖出GMP快的原因,使用最笨的办法,在调试器中打开反汇编窗口,一步一步跟踪,找出GMP对应函数的汇编代码,没费太多工夫,居然找到其代码。见下,为了便于阅读,加上了标号和少许注释。
  1. fun_start:
  2. D570:
  3.         mov eax, dword ptr [esp+10h]
  4.         sub esp,14h
  5.         mov dword ptr [esp+0ch],esi
  6.         mov esi, dword ptr [esp+1ch]
  7.         mov dword ptr [esp+10h],ebx
  8.         mov ebx,dword ptr [esp+20h]
  9.         bsf ecx,eax
  10.         mov dword ptr [esp+4],ebp
  11.         shr eax,cl
  12.         mov edx,eax
  13.         shr eax,1
  14.         mov dword ptr [esp+24h],edx
  15.         and eax,7fh
  16.         movzx ebp, byte ptr [eax+TAB_NAME]

  17.         lea eax,[ebp+ebp]
  18.         imul ebp,ebp
  19.         mov dword ptr [esp+8],edi
  20.         mov edi, dword ptr [esp+18h]
  21.         lea esi, [esi+ebx*4]
  22.         imul ebp, dword ptr [esp+24h]
  23.         sub eax,ebp
  24.        
  25.         lea ebp, [eax+eax]
  26.         imul eax,eax
  27.         lea edi, [edi+ebx+4]
  28.         neg ebx
  29.         mov dword ptr [esp+18h],edi
  30.         imul eax,dword ptr [esp+24h]
  31.         sub  ebp,eax
  32.         mov dword ptr [esp],ebp
  33.         mov eax,dword ptr [esi+ebx*4]
  34.         or  ecx,ecx                ; shift=0
  35.        
  36.         jne  D60D        ; shift!=0
  37.         jmp  D5ED        ; shift_0_update

  38. D5DD:

  39. shfit_is_0:
  40.         mul eax, dword ptr [esp+24h]
  41.         mov eax,dword ptr [esi+ebx*4]
  42.         sub eax,ecx
  43.         sbb ecx,ecx
  44.         sub eax,edx
  45.         sbb ecx,0

  46. D5ED:
  47. shift_0_MID:
  48.         imul eax,dword ptr [esp]
  49.         mov dword ptr [edi+ebx*4],eax
  50.         neg ecx
  51.         inc ebx
  52.         jne D5DD

  53.         mov esi, dword ptr [esp+0CH]
  54.         mov edi, dword ptr [esp+8]
  55.         mov ebp, dword ptr [esp+4]
  56.         mov ebx, dword ptr [esp+10h]
  57.         add esp,14h
  58.         ret


  59. D60D:
  60. shift_isnot_0:
  61.         xor ebp,ebp
  62.         xor edx,edx
  63.         inc ebx
  64.         je  D64B                ;        转收尾处理
  65.        
  66.         mov edi, dword ptr [esi+ebx*4]
  67.         shrd eax,edi, cl
  68.         jmp D633

  69. D61C:   ; loop_start       
  70.         mov edi, dword ptr [esi+ebx*4]
  71.         mul eax, dword ptr [esp+24h]
  72.         mov eax, dword ptr [esi+ebx*4-4]
  73.         shrd eax,edi, cl
  74.         sub eax,ebp
  75.         sbb ebp,ebp
  76.         sub eax,edx
  77.         sub ebp,0

  78. D633:       
  79.         imul eax, dword ptr [esp]
  80.         mov edi, dword ptr [esp+18h]
  81.         neg ebp
  82.         mov dword ptr [edi+ebx*4-4],eax
  83.         inc ebx
  84.         jne D61c
  85.        
  86.         mul eax, dword ptr [esp+24h]
  87.         mov eax, dword ptr [esi-4]

  88. D64B:       
  89.         shr eax,cl
  90.         mov esi, dword ptr [esp+0ch]
  91.         sub eax,ebp
  92.         mov ebp, dword ptr [esp+4]
  93.         sub eax,edx
  94.         mov ebx, dword ptr [esp+10h]
  95.         imul eax, dword ptr [esp]
  96.         mov dword ptr [edi-4],eax
  97.         mov edi, dword ptr [esp+8]
  98.         add esp, 14h
  99.         ret
  100. :fun_end
  101.        
复制代码


可以看到,对于奇数分支,GMP中循环块包含 如下11条指令。
  1.         mul eax, dword ptr [esp+24h]
  2.         mov eax,dword ptr [esi+ebx*4]
  3.         sub eax,ecx
  4.         sbb ecx,ecx
  5.         sub eax,edx
  6.         sbb ecx,0
  7.         imul eax,dword ptr [esp]
  8.         mov dword ptr [edi+ebx*4],eax
  9.         neg ecx
  10.         inc ebx
  11.         jne D5DD
复制代码

我的版本有12条指令,仅仅比GMP多1条指令,但是为什么速度相差很大呢?
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2016-11-25 16:48:34 | 显示全部楼层
1. 将
   shld edx,eax,2
改为
  shr  eax,30"
        shl  edx,2
        or   edx,eax
       
2. 将
   "a=a+b", "c-=a"
改为
  "c-=b,c-=a"

最终指令数从12变为15条,增加了3条指令,但是速度却提升了,耗时从9.8个周期降到8.5个周期。


继续做实验,删除那3条移位指令,而不考虑其正确性,至此只比GMP的版本多一条指令,测试发现,耗时降为6.8个时钟周期。
最后删除一条and指令,和GMP完全相同,耗时降为6.06个时钟周期,比GMP慢了6%,和理论相符。因为GMP采用2^32,故平均耗时很小。

结论:
1. shld指令较慢,换成单精度指令shl,shr和or组合更好。
2. 降低指令依赖,尽可能晚的使用耗时指令的计算结果。
3. 虽然在多数情况下,减少指令条数可获得性能的提升。但依赖链比较长的情况下,具有较少的指令的指令组合可能比具有较多指令的指令组合(弱依赖)更慢。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2016-11-25 16:51:44 | 显示全部楼层
最后,贴出SSE2版本的汇编代码。
  1. ;  Exact Division,
  2. ; _declspec(naked)  DWORD __mpn_B30_exact_div1_core_SSE2 (DWORD *rp, const DWORD *sp, DWORD n, DWORD d,  DWORD di, int shift)
  3. __mpn_B30_exact_div1_core_SSE2 PROC
  4. PARAM_rp    = 4
  5. PARAM_sp    = 8
  6. PARAM_len        = 12
  7. PARAM_d                = 16
  8. PARAM_di        = 20
  9. PARAM_shift = 24

  10. REG_sp                EQU esi
  11. REG_rp                EQU edi
  12. REG_I                EQU ebx


  13. XMM_TMP                EQU XMM0
  14. XMM_TMP2        EQU XMM1
  15. XMM_CARRY        EQU XMM2

  16. XMM_D                EQU XMM4
  17. XMM_DI                EQU XMM5
  18. XMM_SHIFT        EQU XMM6
  19. XMM_MASK        EQU XMM7

  20. IF_SIZE                = 16
  21.         push esi                                                ; save registers
  22.         push edi
  23.         push ebx
  24.         push ebp

  25.         mov  REG_rp,     [esp+IF_SIZE+PARAM_rp]
  26.         mov  REG_sp,     [esp+IF_SIZE+PARAM_sp]
  27.         mov  eax,        [esp+IF_SIZE+PARAM_len]
  28.         PXOR XMM_carry, XMM_carry
  29.         or   eax,eax       
  30.         jz   funExit1        ; directly exit if len=0

  31.         mov   eax, MASK_B30
  32.         mov   ecx, DP [esp+IF_SIZE+PARAM_shift]
  33.         MOVD  XMM_D        , DP [esp+IF_SIZE+PARAM_d]
  34.         MOVD  XMM_DI, DP [esp+IF_SIZE+PARAM_di]
  35.         MOVD  XMM_MASK,  eax
  36.         MOVD  XMM_SHIFT, ecx

  37.         mov  REG_I,  [esp+IF_SIZE+PARAM_len]
  38.         lea  REG_rp, [REG_rp+REG_I*4]
  39.         lea  REG_sp, [REG_sp+REG_I*4]
  40.         neg  REG_I

  41.         or   ecx, ecx
  42.         jnz  shift_isnot_0

  43. shift_is_0:
  44.         MOVD XMM_TMP, DP [REG_sp+REG_I*4]                ; u = up[0];
  45.         PMULUDQ XMM_TMP,XMM_DI                        ; l = (u * di)
  46.         PAND XMM_TMP, XMM_MASK                        ; l = (u * di) & MASK_B30;
  47.         MOVD DP [REG_rp+REG_I*4],XMM_TMP                ; rp[0]=l
  48.         PXOR XMM_CARRY,XMM_CARRY                ; c=0
  49.         add  REG_I,1
  50.         jz   funExit1  

  51. shift0_body_start:
  52.         PMULUDQ XMM_TMP,XMM_D                        ; tmp = prod =(UINT64)(l)* (UINT64)(d);        
  53.         PSRLQ   XMM_TMP,30                                ; tmp=(DWORD)(prod>>30);
  54.        
  55.         MOVD    XMM_TMP2,DP [REG_sp+REG_I*4]        ; u = sp[i];
  56.         PSUBD   XMM_TMP2,XMM_carry                        ; l = u - c;
  57.         PSUBD   XMM_TMP2,XMM_TMP                        ; tmp2 = u -  c - (prod>>30)
  58.         MOVDQA  XMM_CARRY,XMM_TMP2                        ;  carry=l
  59.         PMULUDQ XMM_TMP2,XMM_DI                                ;  tmp= (l * di)
  60.         PSRLD   XMM_CARRY,31                                        ; if u<c, set c=1 else c=0        
  61.         PAND    XMM_TMP2, XMM_MASK                        ; tmp= (l * di) & MASK_B30;
  62.         MOVD    DP [REG_rp+REG_I*4],XMM_TMP2        ; rp[i] =tmp2= l;
  63.         MOVDQA  XMM_TMP, XMM_TMP2                        ; tmp=l

  64.         add    REG_I,1
  65.         jnz   shift0_body_start

  66. funExit1:
  67.         MOVD  eax, XMM_carry
  68.         pop  ebp                                                ; restore registers
  69.         pop  ebx
  70.         pop  edi
  71.         pop  esi
  72.         ret
  73.        
  74. shift_isnot_0:
  75.         PXOR XMM_CARRY,XMM_CARRY                ; c=0
  76.         MOVD XMM_TMP, DP [REG_sp+REG_I*4]                ; u = up[0];
  77.         add  REG_I,1                                        ; i++
  78.         jz   shift_n0_tail

  79. shift_n0_body_start:
  80.         MOVD        XMM_TMP2, DP [REG_sp+REG_I*4]        ; u_next=rp[i]
  81.         PSLLQ   XMM_TMP2, 30                        ; u_next<< 30
  82.         PADDQ        XMM_TMP, XMM_TMP2                ; u= (u_next<<30)+u
  83.         PSRLQ   XMM_TMP, XMM_SHIFT                ; u = (u>>shift)
  84.         PAND        XMM_TMP, XMM_MASK                ; u = (u>>shift) & MASK_B30;
  85.         PSUBD        XMM_TMP, XMM_CARRY                ; l= u-c
  86.        
  87.         MOVDQA  XMM_CARRY, XMM_TMP               
  88.         PMULUDQ XMM_TMP,XMM_DI                        ; tmp= l*di
  89.         PSRLD   XMM_CARRY, 31                    ; if u<c, set c=1 else c=0        
  90.        
  91.         MOVD   XMM_TMP2, DP [REG_sp+REG_I*4]        ; u_next= [REG_sp]
  92.         PAND   XMM_TMP, XMM_MASK                ; tmp= l = (l * di) & MASK_B30;
  93.         MOVD   DP [REG_rp+REG_I*4-4],XMM_TMP               
  94.         PMULUDQ XMM_TMP,XMM_D                        ; tmp=l*d;
  95.         PSRLQ  XMM_TMP,30                                ; tmp=prod>>30
  96.         PADDD  XMM_CARRY,XMM_TMP                ; c += (prod>>30);
  97.         MOVDQA XMM_TMP, XMM_TMP2                ; u= u_next

  98.         add    REG_I,1
  99.         jnz    shift_n0_body_start

  100. shift_n0_tail:
  101.         PSRLQ   XMM_TMP, XMM_SHIFT                        ; u = (u>>shift)
  102.         PSUBD        XMM_TMP, XMM_CARRY                        ; tmp=l=u-c
  103.         PMULUDQ XMM_TMP, XMM_DI                                ; tmp=l= l*di
  104.         PAND        XMM_TMP, XMM_MASK                        ; l= (l * di) & MASK_B30;
  105.         MOVD        DP [REG_rp+REG_I*4-4], XMM_TMP                ; rp[i]=l
  106.         MOVD   eax, XMM_carry                                ; return c

  107. funExit2:
  108.         pop  ebp                                                ; restore registers
  109.         pop  ebx
  110.         pop  edi
  111.         pop  esi
  112.         ret
  113. __mpn_B30_exact_div1_core_SSE2 ENDP
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2016-11-25 17:01:57 | 显示全部楼层
贴出算法发明者Tudor Jebelean 的论文,论文是1992年发表的,算是比较新的算法。

1-s2.0-S0747717183710126-main.pdf

302.26 KB, 阅读权限: 20, 下载次数: 3, 下载积分: 金币 -1 枚, 经验 1 点, 下载 1 次

毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2016-12-4 11:10:27 | 显示全部楼层
这个算法,在 CSDN 上讨论“分数化小数”的擂台中,mathe提及了循环节“逆序输出”的算法,当时很震撼,原理应该与楼主说的一致。
后来,我借用该算法,专门写了个小程序:关于“擂台:分数化小数”,我专门写了一个快速程序(下载见内)
其原始讨论贴,我搜了下,为:擂台:分数化小数
上述讨论,是2007年5月份,距今已10年(严格点,是九年半).
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2016-12-4 21:03:31 | 显示全部楼层
看了一下你的那个帖子,其实质是求离散对数。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
您需要登录后才可以回帖 登录 | 欢迎注册

本版积分规则

小黑屋|手机版|数学研发网 ( 苏ICP备07505100号 )

GMT+8, 2024-4-25 20:15 , Processed in 0.055583 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表