找回密码
 欢迎注册
查看: 49480|回复: 33

[原创] 求一个数组中元素的最大值和最小值

[复制链接]
发表于 2010-12-14 22:53:56 | 显示全部楼层 |阅读模式

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

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

×
  求一个数组最大值最小值的问题是一个复杂度为O(n)的问题,从算法角度上讲,很难有实质性的改进。本文试图使用几种方法来优化算法,使得查找速度尽可能快,并给出源代码和运行结果。也欢迎大家给出你们自己的算法。
精华

  现在,我已经编写了6个函数。
  函数getMaxMin1使用最普通的算法查找最大最小值。
  函数getMaxMin_SSE4 则使用SSE4指令优化查找算法,主要使用了SSE4 指令PMINUD和PMAXUD,一次可求4个DWORD的最大或者最小值,内存读则使用movdqa指令,一次读取4个DWORD。
  函数getMaxMin_MT2_v1是getMaxMin1 的2线程版本。
  函数getMaxMin_MT2_v4是getMaxMin1 的4线程版本。
  函数getMaxMin_MT2_v2是getMaxMin_SSE4 的2线程版本。
  函数getMaxMin_MT4_v2是getMaxMin_SSE4 的4线程版本。


测试结果:
  采用32M的测试样本,运行在Intel Core 2 Duo E8500 双核CPU,同时测试这6个函数。每个函数均取5次测试结果的最小值,结果如下:
getMaxMin1:       0.0517秒
getMaxMin_SSE4:   0.0205秒
getMaxMin_MT2_v1: 0.0250秒
getMaxMin_MT4_v1  0.0293秒
getMaxMin_MT2_v2: 0.0200秒
getMaxMin_MT4_v2: 0.0201秒

结果分析:
  • getMaxMin_SSE4的速度是普通C版的函数的250%,但此函数采用双线程后,速度并没有提高。
  • getMaxMin_MT2_v1这个2线程的版本速度为单线程版本的200%,比较合乎逻辑
  • getMaxMin_MT4_v1  这个4线程的版本速度比2线程版本更慢。这也合乎逻辑,在双核CPU上,4线程并不能提高速度。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2010-12-14 22:55:44 | 显示全部楼层
findMaxMin.h 文件定义
  1. ]#if !defined(AFX_FINDMAXMINMT_H__11572878_BB5B_4BB3_A31E_16ED572B10FC__INCLUDED_)
  2. #define AFX_FINDMAXMINMT_H__11572878_BB5B_4BB3_A31E_16ED572B10FC__INCLUDED_

  3. #if _MSC_VER > 1000
  4. #pragma once
  5. #endif // _MSC_VER > 1000

  6. #include <windows.h>
  7. #include "defs.h"

  8. #define TF_FIND_MAX_MIN_ALU     1
  9. #define TF_FIND_MAX_MIN_SSE4    2

  10. typedef struct _thread_function_ST
  11. {
  12.     DWORD fun_ID;
  13.     DWORD *pdata;
  14.     int   len;
  15. }THREAD_FUNCTION_ST;

  16. typedef struct _search_sort_ST
  17. {
  18.     DWORD fun_ID;
  19.     DWORD *pdata;
  20.     int   len;
  21.     DWORD min;
  22.     DWORD max;
  23. }SEARCH_SORT_ST;

  24. extern DWORD WINAPI ThreadFunc(LPVOID n);

  25. extern void getMaxMin1(DWORD *pData,int len,DWORD *min,DWORD *max);
  26. extern void getMaxMin_asm(DWORD *pData,int len,DWORD *min,DWORD *max);

  27. extern void getMaxMin_SSE4(DWORD *pData,int len,DWORD *min,DWORD *max);
  28. extern void getMaxMin_MT2_v1(DWORD *pData,int len,DWORD *min,DWORD *max);
  29. extern void getMaxMin_MT4_v1(DWORD *pData,int len,DWORD *min,DWORD *max);
  30. extern void getMaxMin_MT2_v2(DWORD *pData,int len,DWORD *min,DWORD *max);
  31. extern void getMaxMin_MT4_v2(DWORD *pData,int len,DWORD *min,DWORD *max);



  32. #endif // !defined(AFX_FINDMAXMINMT_H__11572878_BB5B_4BB3_A31E_16ED572B10FC__INCLUDED_)
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2010-12-14 22:58:26 | 显示全部楼层
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <assert.h>
  4. #include <windows.h>

  5. #include "defs.h"
  6. #include "findMaxMin.h"
  7. #include "MTVERIFY.h"


  8. DWORD WINAPI ThreadFunc(LPVOID n)
  9. {
  10.     THREAD_FUNCTION_ST *pTF=(THREAD_FUNCTION_ST *)n;
  11.     SEARCH_SORT_ST *p;

  12.     switch (pTF->fun_ID)
  13.     {
  14.     case TF_FIND_MAX_MIN_ALU:
  15.         p=(SEARCH_SORT_ST *)n;
  16.         getMaxMin1(p->pdata,p->len,&(p->min),&(p->max));
  17.         break;
  18.     case TF_FIND_MAX_MIN_SSE4:
  19.         p=(SEARCH_SORT_ST *)n;
  20.         getMaxMin_SSE4(p->pdata,p->len,&(p->min),&(p->max));
  21.         break;
  22.     default:
  23.         printf("Invalid thread function ID in %d line\n",__LINE__);

  24.     }
  25.     return 0;
  26. }


  27. void getMaxMin1(DWORD *pData,int len,DWORD *min,DWORD *max)
  28. {
  29.     int i;
  30.     *min=~0;
  31.     *max=0;
  32.     for (i=0; i<len; i++)
  33.     {
  34.         if ( pData[ i ] > *max)
  35.             *max=pData[ i ];
  36.         if (pData[ i ]< *min)
  37.             *min=pData[ i ];
  38.     }
  39. }

  40. void getMaxMin_asm(DWORD *pData,int len,DWORD *min,DWORD *max)
  41. {
  42.     DWORD _min, _max;
  43.     __asm
  44.     {
  45.         mov esi, pData
  46.         mov ecx, len
  47.         lea edi, [esi+ecx*4]

  48.         mov eax, 0xffffffff        //min
  49.         mov edx, 0                //max

  50. loop_start:
  51.         cmp esi, edi
  52.         jge next10

  53.         cmp [esi], eax
  54.         cmovb eax,[esi]

  55.         cmp [esi], edx
  56.         cmova edx,[esi]

  57.         add esi, 4
  58.         jmp loop_start
  59. next10:
  60.         mov _min, eax
  61.         mov _max, edx
  62.     }

  63.     *min=_min;
  64.     *max=_max;

  65. }

  66. _declspec(naked)
  67. void getMaxMin_SSE4(DWORD *pData,int len,DWORD *min,DWORD *max)

  68. // 使用SSE4.1 指令 计算数组的最大最小值
  69. // 寄存器的使用
  70. // eax: 存放最小值
  71. // edx: 存放最大值
  72. // xmm0: 当前取得的4个整数
  73. // xmm1: 存放最小值
  74. // xmm2: 存放最大值

  75. /* PMINUD and PMAXUD are SSE4.1 instruciton
  76. The usage for PMINUD
  77. Compares packed unsigned dword integers in the destination operand (first operand)
  78. and the source operand (second operand), and returns the minimum for each packed
  79. value in the destination operand.

  80. Operation
  81. IF (DEST[31:0] < SRC[31:0])
  82. THEN DEST[31:0] &#1048773; DEST[31:0];
  83. ELSE DEST[31:0] &#1048773; SRC[31:0]; FI;

  84. IF (DEST[63:32] < SRC[63:32])
  85. THEN DEST[63:32] &#1048773; DEST[63:32];
  86. ELSE DEST[63:32] &#1048773; SRC[63:32]; FI;

  87. IF (DEST[95:64] < SRC[95:64])
  88. THEN DEST[95:64] &#1048773; DEST[95:64];
  89. ELSE DEST[95:64] &#1048773; SRC[95:64]; FI;

  90. IF (DEST[127:96] < SRC[127:96])
  91. THEN DEST[127:96] &#1048773; DEST[127:96];
  92. ELSE DEST[127:96] &#1048773; SRC[127:96]; FI;
  93. */

  94. #define MIN_128REG      xmm1
  95. #define MAX_128REG      xmm2
  96. #define MIN_32REG      eax
  97. #define MAX_32REG      edx
  98. #define PT_REG          esi
  99. #define END_REG          edi

  100. #define PAR_PDATA     4
  101. #define PAR_LEN       8
  102. #define PAR_MIN       12
  103. #define PAR_MAX       16

  104. {
  105.     __asm
  106.     {
  107.         push        esi
  108.         push        edi

  109.         mov            MIN_32REG,  0xffffffff                // min=0xffffffff
  110.         xor            MAX_32REG,    MAX_32REG                // max=0

  111. //phase1:
  112.         mov         ecx, dword ptr[esp+8+PAR_LEN]         // len
  113.         mov         PT_REG, dword ptr[esp+8+PAR_PDATA]   // pData
  114.         lea            END_REG, [PT_REG+ecx*4]                 // edi=pData + len
  115.         jmp            cmp10

  116. loop1_start:
  117.         cmp [PT_REG], MIN_32REG
  118.         cmovb MIN_32REG,[PT_REG]

  119.         cmp [PT_REG], MAX_32REG
  120.         cmova MAX_32REG,[PT_REG]

  121.         add PT_REG, 4
  122. cmp10:
  123.         test PT_REG, 0x0f
  124.         jz phase2

  125.         cmp PT_REG, END_REG
  126.         jb loop1_start

  127. phase2:
  128.         mov         ecx, dword ptr[esp+8+PAR_LEN]        // len
  129.         mov         END_REG, dword ptr[esp+8+PAR_PDATA] // pData
  130.         lea            END_REG, [END_REG+ecx*4]            // edi=pData + len
  131.         and            END_REG, 0xfffffff0                    // clear bit0-bit3 and make edi % 16==0

  132.         movd        MIN_128REG,    MIN_32REG
  133.         pshufd      MIN_128REG, MIN_128REG, 00000000b       // xmm1 = R0:R0:R0:R0
  134.         movd        MAX_128REG,    MAX_32REG
  135.         pshufd      MAX_128REG, MAX_128REG, 00000000b       // xmm2 = R0:R0:R0:R0
  136.         jmp            cmp20

  137. loop2_start:
  138.         movdqa      xmm0, xmmword ptr[PT_REG]
  139.         PMINUD      MIN_128REG, xmm0
  140.         PMAXUD      MAX_128REG, xmm0

  141.         add            PT_REG,16
  142. cmp20:
  143.         cmp            PT_REG, END_REG
  144.         jb            loop2_start


  145. phase3:
  146.         movd        ecx, MIN_128REG
  147.         cmp            ecx, MIN_32REG
  148.         cmovb        MIN_32REG,ecx            //MIN_32REG=min(min_32REG,bit0-31(MIN_128REG))

  149.         psrldq      MIN_128REG, 4            //MIN_128REG >>=4
  150.         movd        ecx, MIN_128REG
  151.         cmp            ecx, MIN_32REG
  152.         cmovb        MIN_32REG,ecx            //MIN_32REG=min(min_32REG,bit32-63(MIN_128REG))

  153.         psrldq      MIN_128REG, 4            //MIN_128REG >>=4
  154.         movd        ecx, MIN_128REG
  155.         cmp            ecx, MIN_32REG
  156.         cmovb        MIN_32REG,ecx            //MIN_32REG=min(min_32REG,bit64-95(MIN_128REG))

  157.         psrldq      MIN_128REG, 4            //MIN_128REG >>=4
  158.         movd        ecx, MIN_128REG
  159.         cmp            ecx, MIN_32REG
  160.         cmovb        MIN_32REG,ecx            //MIN_32REG=min(min_32REG,bit96-127(MIN_128REG)    )

  161.         //-----------------------------------------------
  162.         movd        ecx, MAX_128REG
  163.         cmp            ecx, MAX_32REG
  164.         cmova        MAX_32REG,ecx            //MAX_32REG=max(max_32REG,bit0-31(MAX_128REG))

  165.         psrldq      MAX_128REG, 4            //MAX_128REG >>=4
  166.         movd        ecx, MAX_128REG
  167.         cmp            ecx, MAX_32REG
  168.         cmova        MAX_32REG,ecx            //MAX_32REG=max(max_32REG,bit32-63(MAX_128REG))


  169.         psrldq      MAX_128REG, 4            //MAX_128REG >>=4
  170.         movd        ecx, MAX_128REG
  171.         cmp            ecx, MAX_32REG
  172.         cmova        MAX_32REG,ecx            //MAX_32REG=max(max_32REG,bit64-95(MAX_128REG))

  173.         psrldq      MAX_128REG, 4            //MAX_128REG >>=4
  174.         movd        ecx, MAX_128REG
  175.         cmp            ecx, MAX_32REG
  176.         cmova        MAX_32REG,ecx            //MIN_32REG=max(max_32REG,bit96-127(MIN_128REG))

  177.         mov         END_REG, dword ptr[esp+8+PAR_PDATA]    // pData
  178.         mov         ecx, dword ptr[esp+8+PAR_LEN]        // len
  179.         lea            END_REG, [END_REG+ecx*4]            // edi=pData + len
  180.         jmp            cmp30

  181. loop3_start:
  182.         cmp [PT_REG], MIN_32REG
  183.         cmovb MIN_32REG,[PT_REG]

  184.         cmp [PT_REG], MAX_32REG
  185.         cmova MAX_32REG,[PT_REG]

  186.         add PT_REG, 4
  187. cmp30:
  188.         cmp PT_REG, END_REG
  189.         jb loop3_start

  190. RET_VALUE:
  191.         mov        ecx, dword ptr[esp+8+PAR_MIN]        //*min
  192.         mov        dword ptr [ecx],MIN_32REG

  193.         mov        ecx, dword ptr[esp+8+PAR_MAX]        //*max
  194.         mov        dword ptr [ecx],MAX_32REG

  195.         emms
  196.         pop        edi
  197.         pop        esi

  198.         ret
  199.     }
  200. }



  201. void getMaxMin_MT_n(int threadCount,int version, DWORD *pData,int len,DWORD *min,DWORD *max)
  202. {
  203.     int slot,width;
  204.     SEARCH_SORT_ST thrdPara[16];
  205.     HANDLE  hThrds[16];
  206.     DWORD   threadId;

  207.     assert(threadCount<=16);
  208.     width= (len + threadCount-1)/threadCount;

  209.     for (slot=0;slot<threadCount;slot++)
  210.     {
  211.         if (version==1)
  212.             thrdPara[slot].fun_ID=TF_FIND_MAX_MIN_ALU;
  213.         else
  214.             thrdPara[slot].fun_ID=TF_FIND_MAX_MIN_SSE4;

  215.         thrdPara[slot].pdata=pData+slot*width;
  216.         thrdPara[slot].len=width;
  217.     }

  218.     if (len % width !=0)
  219.     {
  220.         thrdPara[threadCount-1].len=len % width;
  221.     }

  222.     for (slot=0;slot<threadCount;slot++)
  223.     {
  224.         MTVERIFY( hThrds[slot] = CreateThread(NULL,
  225.             0,
  226.             ThreadFunc,
  227.             (LPVOID)(thrdPara+slot),
  228.             0,
  229.             &threadId ) );
  230.     }

  231.     for (slot=0; slot<threadCount; slot++)
  232.     {
  233.         WaitForSingleObject(hThrds[slot], INFINITE);
  234.         MTVERIFY( CloseHandle(hThrds[slot]) );
  235.     }

  236.     *min=thrdPara[0].min;
  237.     *max=thrdPara[0].max;
  238.     for (slot=1;slot<threadCount;slot++)
  239.     {
  240.         if (thrdPara[slot].max > *max)
  241.             *max=thrdPara[slot].max;
  242.         if (thrdPara[slot].min < *min)
  243.             *min=thrdPara[slot].min;
  244.     }
  245. }

  246. void getMaxMin_MT2_v1(DWORD *pData,int len,DWORD *min,DWORD *max)
  247. {
  248.     getMaxMin_MT_n(2,1,pData,len,min,max);
  249. }

  250. void getMaxMin_MT4_v1(DWORD *pData,int len,DWORD *min,DWORD *max)
  251. {
  252.     getMaxMin_MT_n(4,1,pData,len,min,max);
  253. }


  254. void getMaxMin_MT2_v2(DWORD *pData,int len,DWORD *min,DWORD *max)
  255. {
  256.     getMaxMin_MT_n(2,2,pData,len,min,max);
  257. }

  258. void getMaxMin_MT4_v2(DWORD *pData,int len,DWORD *min,DWORD *max)
  259. {
  260.     getMaxMin_MT_n(4,2,pData,len,min,max);
  261. }
复制代码
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2010-12-15 07:42:13 | 显示全部楼层
注意,在排版代码时,应先在 UEStudio 之类的编辑器中将^t(Tab符)替换成四个空格。
因为论坛里,会将Tab符自动替换成8个空格,使代码缩进过大,不利于阅读。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2010-12-15 12:43:39 | 显示全部楼层
好久没贴代码了,下次注意。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2010-12-15 14:41:41 | 显示全部楼层
多线程对这个问题没帮助

多核在提高到某种程度后,会内存读速度
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2010-12-15 15:09:30 | 显示全部楼层
觉得此问题最后仍归结到存取问题.....mark,n天后继续。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2010-12-15 18:56:32 | 显示全部楼层
32M个整数共32*4=128M,最快用时0.02秒,故内存读取速度为128M/0.02秒=6400MB/s=6.4GB/s
这台电脑的内存为DDR2-800, 其内存带宽为800M/s * 64bit / (8bit/B)= 6.4GB/s。
哈哈,我的 那个求最大值最小值函数的SSE4版本 用尽了内存带宽,故内存成为瓶颈,多线程不起作用。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2010-12-15 19:38:39 | 显示全部楼层
贴上我的电脑的内存性能报告,使用 EVEREST 5.01测试。
mypc_memtest.PNG
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2010-12-15 20:18:57 | 显示全部楼层
奇怪,为什么 Memory 的 read 尚不及 write 快?
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
您需要登录后才可以回帖 登录 | 欢迎注册

本版积分规则

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

GMT+8, 2024-4-26 17:58 , Processed in 0.048700 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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