找回密码
 欢迎注册
楼主: 无心人

[擂台] 大数运算基选择测试

[复制链接]
发表于 2008-4-10 20:39:56 | 显示全部楼层
在38楼给出几个 以$2^32$为基,使用 MMX寄存器,可计算480bit ×480bit 的函数。其中
UInt480x480_MMX是一个使用完全循环展开,仅能计算480bit的数的版本;而UIntMul_basecase_MMX2是一个不使用循环展开,可计算任意长度的数的版本。
现在,楼主又给出一个使用4路循环展开的版本,理论上这个版本在 计算480bit × 480 bit,应该比 UIntMul_basecase_MMX2 快一些,但慢于UInt480x480_MMX

38#给出的这两个函数的原型是:
void UInt480x480_MMX( unsigned long * result, const unsigned long *  left, const unsigned long *  right );
void UIntMul_basecase_MMX2( unsigned long * result, const unsigned long *  left, const unsigned long *  right,
           unsigned long leftLen,  unsigned long rightLen );
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2008-4-10 20:44:30 | 显示全部楼层
对, 你的分析很对
看并行4路的效率了
在加法是1/6的增幅
乘法估计要更多
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2008-4-10 21:11:02 | 显示全部楼层
乘法肯定是比1/6少。

循环展开能够提高速度的原因在于:
  1.减少了数组指针的递增指令的执行次数,如下面代码中的“lea edi, [edi+4]”,“lea ebx, [ebx+4]”,
  2.减少了比较和条件跳转指令的执行次数。如下面代码中的“sub ecx, 1“,"jnz mbinmul3".
  而其他用来工作的指令则无法减少,另一个好处是,循环展开便于合适的安排指令,使指令间的依赖很小,但这种影响一般很小。

  如果循环体很小,其中几条指令不是很耗时,则减少的这些指令占用的时间比例较大,提速明显。反之则提速不明显。对于加法,真正干活的指令占用时间少,故提升比例较高。而对于乘法,真正用来干活的指令(如pmuludq)占用的时间比例很大。故提升的比例应该很有限。
  1. movd mm2, dword ptr [edi]
  2. lea edi, [edi+4]
  3. movd mm3, dword ptr [ebx]                        
  4. pmuludq mm2, mm1
  5. paddq mm0, mm3
  6. paddq mm0, mm2
  7. movd dword ptr [ebx], mm0
  8. psrlq mm0, 32
  9. lea ebx, [ebx+4]
  10. sub ecx, 1
  11. jnz mbinmul3
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2008-4-10 21:30:14 | 显示全部楼层
可是乘法存在延迟长的问题啊
如果单循环,可能造成整个循环都在等乘法结果
而4个一起,只要一个好了
就能做部分工作,
处理完一个
也许剩下的乘法都不会再产生多余的延迟了

所以到底是你说的情况
还是我说的情况
要测试了才说的清
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2008-4-10 21:32:57 | 显示全部楼层
所以我分析下面的彩色指令都应该不占CPU额外时间
movd mm2, dword ptr [edi]
lea edi, [edi+4]  
movd mm3, dword ptr [ebx]                        
pmuludq mm2, mm1
paddq mm0, mm3
paddq mm0, mm2
movd dword ptr [ebx], mm0
psrlq mm0, 32
lea ebx, [ebx+4]
sub ecx, 1
jnz mbinmul3
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2008-4-10 21:36:11 | 显示全部楼层
执行顺序可能是
1、movd mm2, dword ptr [edi]
1、lea edi, [edi+4]  
1、movd mm3, dword ptr [ebx]                        
2、pmuludq mm2, mm1
2、paddq mm0, mm3
3、paddq mm0, mm2
4、movd dword ptr [ebx], mm0
4、psrlq mm0, 32
4、lea ebx, [ebx+4]
2、sub ecx, 1
5、jnz mbinmul3
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2008-4-11 10:41:45 | 显示全部楼层
理论上,CPU存在多个执行单元,调整指令顺序,减少指令依赖可以提升速度。然而我之前的种种实验表明,这种调整指令顺序,提高并行度的做法并无多大效果,不知何故。

  另一种行之有效的优化循环的办法是减少控制指令,这种做法在某些CPU效果相当明显,我们以本贴子中出现过的程序为例给予说明。

UInt480x480_yaos,UInt480x480_MMX2,UInt480x480_MMX3 这三个函数均为以普通方式计算大数乘法的函数,
程序的逻辑也相同,不过 UInt480x480_yaos多做了一些额外的工作,即需要先将目标buffer清零。


  先看看他们在2种CPU的速度表现,计算2个长为15个DWORD的乘积,运行100万次

                     PIV 2.6           PM1.7
UInt480x480_yaos  698.655 ms        1053.610 ms
UInt480x480_MMX2  671.515 ms       863.055 ms
UInt480x480_MMX3  585.015 ms       798.775 ms

  再分析一下这几个程序的差异,这几个程序均为2重循环,在循环体中,包括真正干活的指令和循环控制的指令,循环控制的指令又分调整数组指针或计数器的指令 和 比较/跳转指令.这3个程序中内循环指令条数分别为11,10,9,其中循环控制指令分别为4,3,2条。

  第一个程序,循环控制指令是 “lea edi, [edi+4]”,“lea ebx, [ebx+4]”,“sub ecx, 1”,“jnz mbinmul3”,共4条。第一个程序,循环控制指令是“inc eax”,“cmp eax,dword ptr [esp + 16+STACK_BASE]”,“jb  innerLoop”。第三个程序循环控制指令是“inc eax”,“jnz innerLoop”。
  第三个程序的速度已经非常逼近完全循环展开的版本,尤其是在PM CPU。在PIV/PM,速度分别达到完全循环展开程序的82%/%95.

  下面给出这三个程序调用到的子程序的完整的代码。
  1. 程序UInt480x480_yaos 调用到的子程序的代码
  2. void  lMul_yaos(uint32  *result,
  3. const uint32  *left,
  4. const uint32  *right,
  5. uint32 sLeft,
  6. uint32 sRight)
  7. {
  8. __asm          {
  9.              mov edx, sLeft
  10.              mov  esi, dword ptr [left]
  11.              mov  edi, dword ptr [right]
  12.              mov  ebx, dword ptr [result]
  13. mbinmul2:
  14.              mov eax, ebx
  15.              pxor mm0, mm0
  16.              mov ecx, sRight                        
  17.              movd mm1, dword ptr [esi]
  18.              movd mm4, edi
  19. mbinmul3:
  20.              movd mm2, dword ptr [edi]
  21.              lea edi, [edi+4]
  22.              movd mm3, dword ptr [ebx]                        
  23.              pmuludq mm2, mm1
  24.              paddq mm0, mm3
  25.              paddq mm0, mm2
  26.              movd dword ptr [ebx], mm0
  27.              psrlq mm0, 32
  28.              lea ebx, [ebx+4]
  29.              sub ecx, 1
  30.              jnz mbinmul3

  31.              movd edi, mm4
  32.              movd dword ptr [ebx], mm0
  33.              mov ebx, eax
  34.              lea esi, [esi+4]
  35.              lea ebx, [ebx+4]
  36.              sub edx, 1
  37.              jnz mbinmul2
  38.              emms
  39.              }
  40. }
复制代码
  1. 程序UInt480x480_MMX2 调用到的子程序的代码

  2. _declspec(naked)
  3. void UIntMul_basecase_MMX2( unsigned long * result,
  4.                            const unsigned long *  left,
  5.                            const unsigned long *  right,
  6.     unsigned long leftLen,
  7.     unsigned long rightLen )
  8. {
  9. #define STACK_BASE 12
  10. __asm
  11.     {
  12.          push esi
  13.          push edi
  14.          push ebp

  15.          mov ebp,  dword ptr [esp + 4+STACK_BASE]    ; result
  16.          mov  esi,  dword ptr [esp + 8+STACK_BASE]    ; left
  17.          mov  edi,  dword ptr [esp + 12+STACK_BASE]   ; right
  18.          mov ecx,  dword ptr [esp + 20+STACK_BASE] ; rightLen

  19. //next10:
  20.          cmp ecx,0
  21.          jz thisExit

  22. //loop1_Init:
  23.          xor eax,eax // j=0;i<leftLen
  24.          pxor mm0,mm0
  25.          movd mm2,[edi]
  26.          jmp loop1_Cmp

  27. loop1:
  28.          movd mm1,[esi+eax*4]
  29.          pmuludq mm1,mm2
  30.          paddq mm0, mm1
  31.          movd [ebp+eax*4],mm0
  32.          inc eax
  33.          psrlq mm0, 32 //carry

  34. loop1_Cmp:
  35.          cmp eax,dword ptr [esp + 16+STACK_BASE] //leftLen
  36.          jb loop1
  37.          movd [ebp+eax*4],mm0

  38. //outLoopInit:
  39.          mov ecx,1 // i=1;i<rightLen
  40.          jmp outLoopCmp

  41. outLoop:
  42.          add ebp,4
  43.          pxor mm0,mm0
  44.          movd mm2,[edi+ecx*4]
  45.          xor eax,eax
  46.          jmp innerLoopCmp

  47. innerLoop:
  48.          movd mm1,[esi+eax*4]
  49.          movd mm3,[ebp+eax*4]
  50.          pmuludq mm1,mm2
  51.          paddq mm0, mm1
  52.          paddq mm0, mm3
  53.          movd [ebp+eax*4],mm0
  54.          inc eax
  55.          psrlq mm0, 32 //carry

  56. innerLoopCmp:
  57.          cmp eax,dword ptr [esp + 16+STACK_BASE] //leftLen
  58.          jb innerLoop
  59.          movd [ebp+eax*4],mm0
  60.          inc ecx
  61. outLoopCmp:
  62.          cmp ecx,dword ptr [esp + 20+STACK_BASE] //rightLen
  63.          jb outLoop

  64. thisExit:

  65.          pop ebp
  66.          pop edi
  67.          pop esi

  68.          emms
  69.          ret
  70. }
  71. }
复制代码
  1. 程序UInt480x480_MMX3 调用到的子程序的代码:

  2. _declspec(naked)
  3. void UIntMul_basecase_MMX3( unsigned long * result,
  4.                            const unsigned long *  left,
  5.                            const unsigned long *  right,
  6.     unsigned long leftLen,
  7.     unsigned long rightLen )
  8. {
  9. #define STACK_BASE 12
  10. __asm
  11.     {
  12.          push esi
  13.          push edi
  14.          push ebp

  15.          mov ebp,  dword ptr [esp + 4+STACK_BASE]    ; result
  16.          mov  esi,  dword ptr [esp + 8+STACK_BASE]    ; left
  17.          mov  edi,  dword ptr [esp + 12+STACK_BASE]   ; right
  18.          mov ecx,  dword ptr [esp + 20+STACK_BASE] ; rightLen

  19. //next10:
  20.          cmp ecx,0
  21.          jz thisExit

  22. //loop1_Init:
  23.          xor eax,eax // j=0;i<leftLen
  24.          pxor mm0,mm0
  25.          movd mm2,[edi]
  26.          jmp loop1_Cmp

  27. loop1:
  28.          movd mm1,[esi+eax*4]
  29.          pmuludq mm1,mm2
  30.          paddq mm0, mm1
  31.          movd [ebp+eax*4],mm0
  32.          inc eax
  33.          psrlq mm0, 32 //carry

  34. loop1_Cmp:
  35.          cmp eax,dword ptr [esp + 16+STACK_BASE] //leftLen
  36.          jb loop1
  37.          movd [ebp+eax*4],mm0

  38. //outLoopInit:
  39.          mov ecx,1 // i=1;i<rightLen
  40.          mov eax,dword ptr [esp + 16+STACK_BASE] //leftLen
  41.          lea esi,[esi+eax*4]
  42.          lea ebp,[ebp+eax*4]
  43.          jmp outLoopCmp

  44. outLoop:
  45.          add ebp,4
  46.          pxor mm0,mm0
  47.          movd mm2,[edi+ecx*4]
  48.          mov eax,dword ptr [esp + 16+STACK_BASE] //leftLen
  49.          neg eax
  50.          jmp innerLoopCmp

  51. innerLoop:
  52.          movd mm1,[esi+eax*4]
  53.          movd mm3,[ebp+eax*4]
  54.          pmuludq mm1,mm2
  55.          paddq mm0, mm1
  56.          paddq mm0, mm3
  57.          movd [ebp+eax*4],mm0
  58.          psrlq mm0, 32 //carry
  59.          inc eax
  60. innerLoopCmp:
  61.          jnz innerLoop
  62.          movd [ebp+eax*4],mm0
  63.          inc ecx
  64. outLoopCmp:
  65.          cmp ecx,dword ptr [esp + 20+STACK_BASE] //rightLen
  66.          jb outLoop

  67. thisExit:

  68.          pop ebp
  69.          pop edi
  70.          pop esi

  71.          emms
  72.          ret
  73. }
  74. }
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2008-4-11 11:27:59 | 显示全部楼层
不起作用的原因是现代CPU乃乱序执行的
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2008-4-11 11:50:10 | 显示全部楼层
我有2台电脑,PIV 和 PM,我在优化程序的时候总是要在2台电脑上测试。某些优化手段在一台电脑上效果很明显,但在另外型号的电脑上可能不明显。现在总结一下这2台电脑的差异。
  PM ADC指令相对较慢,减少ADC指令,提速效果明显,而PIV 则不明显。
  PM 内存子系统相对较慢,减少内存写提速效果明显,而PIV 则不明显。
  PM 指令并行执行的较差,减少指令条数提速效果明显,而PIV 则不明显。
  PIV SSE2 指令中访问 128bit寄存器的指令速度很快,而PM则非常慢,用128bit寄存器优化大数乘法,速度不升反降。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2008-4-11 11:53:59 | 显示全部楼层
PM类似现在Core核心
但属于Core一代
PM的SSE2执行单元少
且其他执行单元少
但解码单元多
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
您需要登录后才可以回帖 登录 | 欢迎注册

本版积分规则

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

GMT+8, 2024-4-19 03:02 , Processed in 0.043313 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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