跨平台使用Intrinsic函数范例3——使用MMX、SSE2指令集 处理 32位整数数组求和

http://blog.csdn.net/zyl910/article/details/8116970

  本文面对对SSE等SIMD指令集有一定基础的读者,以32位整数数组求和为例演示了如何跨平台使用MMX、SSE2指令集。支持vc、gcc编译器,在Windows、Linux、Mac这三大平台上成功运行。

 

一、关键讲解

  前文(http://www.cnblogs.com/zyl910/archive/2012/10/22/simdsumfloat.html)演示了如何使用SSE、AVX指令集 处理 单精度浮点数组求和。现在对其进行改造,使用MMX、SSE2指令集 处理 32位整数数组求和。因程序基本上差不多,文本就不详细讲解了,只说关键变化。

1.1 指令集简介

  先来看看支持32位整数的SIMD的指令集—— MMX指令集支持多种整数类型的运算。MMX定义了64位紧缩整数类型,,对应Intrinsic中的__m64类型,它能一次能处理2个32位整数。 SSE指令集只支持单精度浮点运算,直到SSE2指令集才支持双精度浮点数运算。SSE2定义了128位紧缩整数类型,对应Intrinsic中的__m128i类型,它能一次能处理4个32位整数。 AVX指令集只支持单精度和双精度浮点运算。据说2013年Haswell架构中的AVX2指令集才支持整数运算。

1.2 改造为 SSE2的32位整数代码

  在使用Intrinsic函数时,将 SSE的单精度浮点代码 改造为 SSE2的32位整数代码是很方便的。对比前文与本文的数组求和代码,变更的地方有——

float

int32_t

备注

指令 Intrinsic Asm 指令 Intrinsic Asm
      MMX __m64 MMWORD 类型
    _mm_setzero_si64 PXOR 赋0
    * MOVQ 加载
    _mm_add_pi32 PADDD 加法
SSE __m128 XMMWORD SSE2 __m128i XMMWORD 类型
_mm_setzero_ps XORPS _mm_setzero_si128 PXOR 赋0
_mm_load_ps MOVAPS _mm_load_si128 MOVQ 加载
_mm_add_ps ADDPS _mm_add_epi32 PADDD 加法
AVX __m256 YMMWORD       类型
_mm256_setzero_ps VXORPS     赋0
_mm256_load_ps VMOVAPS     加载
_mm256_add_ps VADDPS     加法

 

  其次,还需要调整一下地址计算。因_mm_load_si128与_mm_load_ps不同,是直接采用__m128i指针一次性处理128位,而不是以元素宽度(如float、int32_t),所以循环与地址计算的代码有较大变化—— 1. p指针的类型由“const float*”变为“const __m128i*”。为了适应_mm_load_si128。 2. q指针的含义发生了变化。现在作为单个数据处理时所用指针,即处理SIMD结果的合并,又处理剩下的数据。 3. p指针移动时直接“p++”。而四路循环版中移动指针是“p+=4”,加载时可以写成“_mm_load_si128(p+1)”,地址计算也很方便。

  例如sumfloat_sse与sumint_sse——

  1. // 单精度浮点数组求和_SSE版.  
  2. float sumfloat_sse(constfloat* pbuf, size_t cntbuf) 
  3.     float s = 0;    // 求和变量.  
  4.     size_t i; 
  5.     size_t nBlockWidth = 4; // 块宽. SSE寄存器能一次处理4个float.  
  6.     size_t cntBlock = cntbuf / nBlockWidth; // 块数.  
  7.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量.  
  8.     __m128 xfsSum = _mm_setzero_ps();   // 求和变量。[SSE] 赋初值0  
  9.     __m128 xfsLoad; // 加载.  
  10.     constfloat* p = pbuf;  // SSE批量处理时所用的指针.  
  11.     constfloat* q; // 将SSE变量上的多个数值合并时所用指针.  
  12.  
  13.     // SSE批量处理.  
  14.     for(i=0; i<cntBlock; ++i) 
  15.     { 
  16.         xfsLoad = _mm_load_ps(p);   // [SSE] 加载  
  17.         xfsSum = _mm_add_ps(xfsSum, xfsLoad);   // [SSE] 单精浮点紧缩加法  
  18.         p += nBlockWidth; 
  19.     } 
  20.     // 合并.  
  21.     q = (constfloat*)&xfsSum; 
  22.     s = q[0] + q[1] + q[2] + q[3]; 
  23.  
  24.     // 处理剩下的.  
  25.     for(i=0; i<cntRem; ++i) 
  26.     { 
  27.         s += p[i]; 
  28.     } 
  29.  
  30.     return s; 
  31.  
  32. // 32位整数数组求和_SSE版.  
  33. int32_t sumint_sse(const int32_t* pbuf, size_t cntbuf) 
  34.     int32_t s = 0;  // 求和变量.  
  35.     size_t i; 
  36.     size_t nBlockWidth = 4; // 块宽. SSE寄存器能一次处理4个int32_t.  
  37.     size_t cntBlock = cntbuf / nBlockWidth; // 块数.  
  38.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量.  
  39.     __m128i xidSum = _mm_setzero_si128();   // 求和变量。[SSE2] PXOR. 赋初值0.  
  40.     __m128i xidLoad;    // 加载.  
  41.     const __m128i* p = (const __m128i*)pbuf;    // SSE批量处理时所用的指针.  
  42.     const int32_t* q;   // 单个数据处理时所用指针.  
  43.  
  44.     // SSE批量处理.  
  45.     for(i=0; i<cntBlock; ++i) 
  46.     { 
  47.         xidLoad = _mm_load_si128(p);    // [SSE2] MOVDQA. 加载.  
  48.         xidSum = _mm_add_epi32(xidSum, xidLoad);    // [SSE2] PADDD. 32位整数紧缩环绕加法.  
  49.         p ++; 
  50.     } 
  51.     // 合并.  
  52.     q = (const int32_t*)&xidSum; 
  53.     s = q[0] + q[1] + q[2] + q[3]; 
  54.  
  55.     // 处理剩下的.  
  56.     q = (const int32_t*)p; 
  57.     for(i=0; i<cntRem; ++i) 
  58.     { 
  59.         s += q[i]; 
  60.     } 
  61.  
  62.     return s; 
// 单精度浮点数组求和_SSE版.
float sumfloat_sse(const float* pbuf, size_t cntbuf)
{
	float s = 0;	// 求和变量.
	size_t i;
	size_t nBlockWidth = 4;	// 块宽. SSE寄存器能一次处理4个float.
	size_t cntBlock = cntbuf / nBlockWidth;	// 块数.
	size_t cntRem = cntbuf % nBlockWidth;	// 剩余数量.
	__m128 xfsSum = _mm_setzero_ps();	// 求和变量。[SSE] 赋初值0
	__m128 xfsLoad;	// 加载.
	const float* p = pbuf;	// SSE批量处理时所用的指针.
	const float* q;	// 将SSE变量上的多个数值合并时所用指针.

	// SSE批量处理.
	for(i=0; i<cntBlock; ++i)
	{
		xfsLoad = _mm_load_ps(p);	// [SSE] 加载
		xfsSum = _mm_add_ps(xfsSum, xfsLoad);	// [SSE] 单精浮点紧缩加法
		p += nBlockWidth;
	}
	// 合并.
	q = (const float*)&xfsSum;
	s = q[0] + q[1] + q[2] + q[3];

	// 处理剩下的.
	for(i=0; i<cntRem; ++i)
	{
		s += p[i];
	}

	return s;
}

// 32位整数数组求和_SSE版.
int32_t sumint_sse(const int32_t* pbuf, size_t cntbuf)
{
	int32_t s = 0;	// 求和变量.
	size_t i;
	size_t nBlockWidth = 4;	// 块宽. SSE寄存器能一次处理4个int32_t.
	size_t cntBlock = cntbuf / nBlockWidth;	// 块数.
	size_t cntRem = cntbuf % nBlockWidth;	// 剩余数量.
	__m128i xidSum = _mm_setzero_si128();	// 求和变量。[SSE2] PXOR. 赋初值0.
	__m128i xidLoad;	// 加载.
	const __m128i* p = (const __m128i*)pbuf;	// SSE批量处理时所用的指针.
	const int32_t* q;	// 单个数据处理时所用指针.

	// SSE批量处理.
	for(i=0; i<cntBlock; ++i)
	{
		xidLoad = _mm_load_si128(p);	// [SSE2] MOVDQA. 加载.
		xidSum = _mm_add_epi32(xidSum, xidLoad);	// [SSE2] PADDD. 32位整数紧缩环绕加法.
		p ++;
	}
	// 合并.
	q = (const int32_t*)&xidSum;
	s = q[0] + q[1] + q[2] + q[3];

	// 处理剩下的.
	q = (const int32_t*)p;
	for(i=0; i<cntRem; ++i)
	{
		s += q[i];
	}

	return s;
}

 

1.3 改造为 MMX版

  将SSE2版代码 改造为 MMX版代码也很方便,按照上一节的表格换用不同的数据类型和函数名,然后再调整一下地址计算就差不多了。

  只不过有两点要注意—— 1. MMX运算结束后,要记得调用_mm_empty(EMMS)清理MMX状态,使后续的浮点运算(FPU)能正常运行。 2. MMX Intrinsic中没有提供_mm_load_si64这样的函数,要想从内存中加载数据到__m64变量,可以直接使用“*(指针)”运算符加载数据,但要保证地址是按8字节对齐的。

  例如sumint_mmx函数(可与上一节的sumint_sse函数进行比较)——

  1. // 32位整数数组求和_MMX版.  
  2. int32_t sumint_mmx(const int32_t* pbuf, size_t cntbuf) 
  3.     int32_t s = 0;  // 求和变量.  
  4.     size_t i; 
  5.     size_t nBlockWidth = 2; // 块宽. MMX寄存器能一次处理2个int32_t.  
  6.     size_t cntBlock = cntbuf / nBlockWidth; // 块数.  
  7.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量.  
  8.     __m64 midSum = _mm_setzero_si64();  // 求和变量。[MMX] PXOR, 赋初值0.  
  9.     __m64 midLoad;  // 加载.  
  10.     const __m64* p = (const __m64*)pbuf;    // MMX批量处理时所用的指针.  
  11.     const int32_t* q;   // 单个数据处理时所用指针.  
  12.  
  13.     // MMX批量处理.  
  14.     for(i=0; i<cntBlock; ++i) 
  15.     { 
  16.         midLoad = *p;   // [MMX] MOVQ. 加载.  
  17.         midSum = _mm_add_pi32(midSum, midLoad); // [MMX] PADDD. 32位整数紧缩环绕加法.  
  18.         p ++; 
  19.     } 
  20.     // 合并.  
  21.     q = (const int32_t*)&midSum; 
  22.     s = q[0] + q[1]; 
  23.  
  24.     // 处理剩下的.  
  25.     q = (const int32_t*)p; 
  26.     for(i=0; i<cntRem; ++i) 
  27.     { 
  28.         s += q[i]; 
  29.     } 
  30.  
  31.     // 清理MMX状态.  
  32.     _mm_empty();    // [MMX] EMMS.  
  33.  
  34.     return s; 
// 32位整数数组求和_MMX版.
int32_t sumint_mmx(const int32_t* pbuf, size_t cntbuf)
{
	int32_t s = 0;	// 求和变量.
	size_t i;
	size_t nBlockWidth = 2;	// 块宽. MMX寄存器能一次处理2个int32_t.
	size_t cntBlock = cntbuf / nBlockWidth;	// 块数.
	size_t cntRem = cntbuf % nBlockWidth;	// 剩余数量.
	__m64 midSum = _mm_setzero_si64();	// 求和变量。[MMX] PXOR, 赋初值0.
	__m64 midLoad;	// 加载.
	const __m64* p = (const __m64*)pbuf;	// MMX批量处理时所用的指针.
	const int32_t* q;	// 单个数据处理时所用指针.

	// MMX批量处理.
	for(i=0; i<cntBlock; ++i)
	{
		midLoad = *p;	// [MMX] MOVQ. 加载.
		midSum = _mm_add_pi32(midSum, midLoad);	// [MMX] PADDD. 32位整数紧缩环绕加法.
		p ++;
	}
	// 合并.
	q = (const int32_t*)&midSum;
	s = q[0] + q[1];

	// 处理剩下的.
	q = (const int32_t*)p;
	for(i=0; i<cntRem; ++i)
	{
		s += q[i];
	}

	// 清理MMX状态.
	_mm_empty();	// [MMX] EMMS.

	return s;
}

 

1.4 环境检查

  最后,别忘了检查环境—— INTRIN_MMX、INTRIN_SSE2 宏是 zintrin.h 提供的,可用来在编译时检测编译器是否支持MMX、SSE2指令集。 simd_mmx、simd_sse_level函数是 ccpuid.h 提供的,可用来在运行时检测当前系统环境是否支持MMX、SSE2指令集。

二、全部代码

2.1 simdsumint.c

  全部代码——

  1. #define __STDC_LIMIT_MACROS 1   // C99整数范围常量. [纯C程序可以不用, 而C++程序必须定义该宏.]  
  2.  
  3. #include <stdlib.h>  
  4. #include <stdio.h>  
  5. #include <time.h>  
  6.  
  7. #include "zintrin.h"  
  8. #include "ccpuid.h"  
  9.  
  10.  
  11. // Compiler name  
  12. #define MACTOSTR(x) #x  
  13. #define MACROVALUESTR(x)    MACTOSTR(x)  
  14. #if defined(__ICL)  // Intel C++  
  15. #  if defined(__VERSION__)  
  16. #    define COMPILER_NAME   "Intel C++ " __VERSION__  
  17. #  elif defined(__INTEL_COMPILER_BUILD_DATE)  
  18. #    define COMPILER_NAME   "Intel C++ (" MACROVALUESTR(__INTEL_COMPILER_BUILD_DATE) ")"  
  19. #  else  
  20. #    define COMPILER_NAME   "Intel C++"  
  21. #  endif    // #  if defined(__VERSION__)  
  22. #elif defined(_MSC_VER) // Microsoft VC++  
  23. #  if defined(_MSC_FULL_VER)  
  24. #    define COMPILER_NAME   "Microsoft VC++ (" MACROVALUESTR(_MSC_FULL_VER) ")"  
  25. #  elif defined(_MSC_VER)  
  26. #    define COMPILER_NAME   "Microsoft VC++ (" MACROVALUESTR(_MSC_VER) ")"  
  27. #  else  
  28. #    define COMPILER_NAME   "Microsoft VC++"  
  29. #  endif    // #  if defined(_MSC_FULL_VER)  
  30. #elif defined(__GNUC__) // GCC  
  31. #  if defined(__CYGWIN__)  
  32. #    define COMPILER_NAME   "GCC(Cygmin) " __VERSION__  
  33. #  elif defined(__MINGW32__)  
  34. #    define COMPILER_NAME   "GCC(MinGW) " __VERSION__  
  35. #  else  
  36. #    define COMPILER_NAME   "GCC " __VERSION__  
  37. #  endif    // #  if defined(_MSC_FULL_VER)  
  38. #else  
  39. #  define COMPILER_NAME "Unknown Compiler"  
  40. #endif  // #if defined(__ICL)   // Intel C++  
  41.  
  42.  
  43. //////////////////////////////////////////////////  
  44. // sumint: 32位整数数组求和的函数  
  45. //////////////////////////////////////////////////  
  46.  
  47. // 32位整数数组求和_基本版.  
  48. //  
  49. // result: 返回数组求和结果.  
  50. // pbuf: 数组的首地址.  
  51. // cntbuf: 数组长度.  
  52. int32_t sumint_base(const int32_t* pbuf, size_t cntbuf) 
  53.     int32_t s = 0;  // 求和变量.  
  54.     size_t i; 
  55.     for(i=0; i<cntbuf; ++i) 
  56.     { 
  57.         s += pbuf[i]; 
  58.     } 
  59.     return s; 
  60.  
  61. #ifdef INTRIN_MMX  
  62. // 32位整数数组求和_MMX版.  
  63. int32_t sumint_mmx(const int32_t* pbuf, size_t cntbuf) 
  64.     int32_t s = 0;  // 求和变量.  
  65.     size_t i; 
  66.     size_t nBlockWidth = 2; // 块宽. MMX寄存器能一次处理2个int32_t.  
  67.     size_t cntBlock = cntbuf / nBlockWidth; // 块数.  
  68.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量.  
  69.     __m64 midSum = _mm_setzero_si64();  // 求和变量。[MMX] PXOR, 赋初值0.  
  70.     __m64 midLoad;  // 加载.  
  71.     const __m64* p = (const __m64*)pbuf;    // MMX批量处理时所用的指针.  
  72.     const int32_t* q;   // 单个数据处理时所用指针.  
  73.  
  74.     // MMX批量处理.  
  75.     for(i=0; i<cntBlock; ++i) 
  76.     { 
  77.         midLoad = *p;   // [MMX] MOVQ. 加载.  
  78.         midSum = _mm_add_pi32(midSum, midLoad); // [MMX] PADDD. 32位整数紧缩环绕加法.  
  79.         p ++; 
  80.     } 
  81.     // 合并.  
  82.     q = (const int32_t*)&midSum; 
  83.     s = q[0] + q[1]; 
  84.  
  85.     // 处理剩下的.  
  86.     q = (const int32_t*)p; 
  87.     for(i=0; i<cntRem; ++i) 
  88.     { 
  89.         s += q[i]; 
  90.     } 
  91.  
  92.     // 清理MMX状态.  
  93.     _mm_empty();    // [MMX] EMMS.  
  94.  
  95.     return s; 
  96.  
  97. // 32位整数数组求和_MMX四路循环展开版.  
  98. int32_t sumint_mmx_4loop(const int32_t* pbuf, size_t cntbuf) 
  99.     int32_t s = 0;  // 返回值.  
  100.     size_t i; 
  101.     size_t nBlockWidth = 2*4;   // 块宽. MMX寄存器能一次处理2个int32_t,然后循环展开4次.  
  102.     size_t cntBlock = cntbuf / nBlockWidth; // 块数.  
  103.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量.  
  104.     __m64 midSum = _mm_setzero_si64();  // 求和变量。[MMX] PXOR, 赋初值0.  
  105.     __m64 midSum1 = _mm_setzero_si64(); 
  106.     __m64 midSum2 = _mm_setzero_si64(); 
  107.     __m64 midSum3 = _mm_setzero_si64(); 
  108.     __m64 midLoad;  // 加载.  
  109.     __m64 midLoad1; 
  110.     __m64 midLoad2; 
  111.     __m64 midLoad3; 
  112.     const __m64* p = (const __m64*)pbuf;    // MMX批量处理时所用的指针.  
  113.     const int32_t* q;   // 单个数据处理时所用指针.  
  114.  
  115.     // SSE批量处理.  
  116.     for(i=0; i<cntBlock; ++i) 
  117.     { 
  118.         midLoad = *p;   // [MMX] MOVQ. 加载.  
  119.         midLoad1 = *(p+1); 
  120.         midLoad2 = *(p+2); 
  121.         midLoad3 = *(p+3); 
  122.         midSum = _mm_add_pi32(midSum, midLoad); // [MMX] PADDD. 32位整数紧缩环绕加法.  
  123.         midSum1 = _mm_add_pi32(midSum1, midLoad1); 
  124.         midSum2 = _mm_add_pi32(midSum2, midLoad2); 
  125.         midSum3 = _mm_add_pi32(midSum3, midLoad3); 
  126.         p += 4; // 四路循环展开.  
  127.     } 
  128.     // 合并.  
  129.     midSum = _mm_add_pi32(midSum, midSum1); // 两两合并(0~1).  
  130.     midSum2 = _mm_add_pi32(midSum2, midSum3);   // 两两合并(2~3).  
  131.     midSum = _mm_add_pi32(midSum, midSum2); // 两两合并(0~3).  
  132.     q = (const int32_t*)&midSum; 
  133.     s = q[0] + q[1]; 
  134.  
  135.     // 处理剩下的.  
  136.     q = (const int32_t*)p; 
  137.     for(i=0; i<cntRem; ++i) 
  138.     { 
  139.         s += q[i]; 
  140.     } 
  141.  
  142.     // 清理MMX状态.  
  143.     _mm_empty();    // [MMX] EMMS.  
  144.  
  145.     return s; 
  146. #endif  // #ifdef INTRIN_MMX  
  147.  
  148.  
  149. #ifdef INTRIN_SSE2  
  150. // 32位整数数组求和_SSE版.  
  151. int32_t sumint_sse(const int32_t* pbuf, size_t cntbuf) 
  152.     int32_t s = 0;  // 求和变量.  
  153.     size_t i; 
  154.     size_t nBlockWidth = 4; // 块宽. SSE寄存器能一次处理4个int32_t.  
  155.     size_t cntBlock = cntbuf / nBlockWidth; // 块数.  
  156.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量.  
  157.     __m128i xidSum = _mm_setzero_si128();   // 求和变量。[SSE2] PXOR. 赋初值0.  
  158.     __m128i xidLoad;    // 加载.  
  159.     const __m128i* p = (const __m128i*)pbuf;    // SSE批量处理时所用的指针.  
  160.     const int32_t* q;   // 单个数据处理时所用指针.  
  161.  
  162.     // SSE批量处理.  
  163.     for(i=0; i<cntBlock; ++i) 
  164.     { 
  165.         xidLoad = _mm_load_si128(p);    // [SSE2] MOVDQA. 加载.  
  166.         xidSum = _mm_add_epi32(xidSum, xidLoad);    // [SSE2] PADDD. 32位整数紧缩环绕加法.  
  167.         p ++; 
  168.     } 
  169.     // 合并.  
  170.     q = (const int32_t*)&xidSum; 
  171.     s = q[0] + q[1] + q[2] + q[3]; 
  172.  
  173.     // 处理剩下的.  
  174.     q = (const int32_t*)p; 
  175.     for(i=0; i<cntRem; ++i) 
  176.     { 
  177.         s += q[i]; 
  178.     } 
  179.  
  180.     return s; 
  181.  
  182. // 32位整数数组求和_SSE四路循环展开版.  
  183. int32_t sumint_sse_4loop(const int32_t* pbuf, size_t cntbuf) 
  184.     int32_t s = 0;  // 返回值.  
  185.     size_t i; 
  186.     size_t nBlockWidth = 4*4;   // 块宽. SSE寄存器能一次处理4个int32_t,然后循环展开4次.  
  187.     size_t cntBlock = cntbuf / nBlockWidth; // 块数.  
  188.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量.  
  189.     __m128i xidSum = _mm_setzero_si128();   // 求和变量。[SSE2] PXOR. 赋初值0.  
  190.     __m128i xidSum1 = _mm_setzero_si128(); 
  191.     __m128i xidSum2 = _mm_setzero_si128(); 
  192.     __m128i xidSum3 = _mm_setzero_si128(); 
  193.     __m128i xidLoad;    // 加载.  
  194.     __m128i xidLoad1; 
  195.     __m128i xidLoad2; 
  196.     __m128i xidLoad3; 
  197.     const __m128i* p = (const __m128i*)pbuf;    // SSE批量处理时所用的指针.  
  198.     const int32_t* q;   // 单个数据处理时所用指针.  
  199.  
  200.     // SSE批量处理.  
  201.     for(i=0; i<cntBlock; ++i) 
  202.     { 
  203.         xidLoad = _mm_load_si128(p);    // [SSE2] MOVDQA. 加载.  
  204.         xidLoad1 = _mm_load_si128(p+1); 
  205.         xidLoad2 = _mm_load_si128(p+2); 
  206.         xidLoad3 = _mm_load_si128(p+3); 
  207.         xidSum = _mm_add_epi32(xidSum, xidLoad);    // [SSE2] PADDD. 32位整数紧缩环绕加法.  
  208.         xidSum1 = _mm_add_epi32(xidSum1, xidLoad1); 
  209.         xidSum2 = _mm_add_epi32(xidSum2, xidLoad2); 
  210.         xidSum3 = _mm_add_epi32(xidSum3, xidLoad3); 
  211.         p += 4; // 四路循环展开.  
  212.     } 
  213.     // 合并.  
  214.     xidSum = _mm_add_epi32(xidSum, xidSum1);    // 两两合并(0~1).  
  215.     xidSum2 = _mm_add_epi32(xidSum2, xidSum3);  // 两两合并(2~3).  
  216.     xidSum = _mm_add_epi32(xidSum, xidSum2);    // 两两合并(0~3).  
  217.     q = (const int32_t*)&xidSum; 
  218.     s = q[0] + q[1] + q[2] + q[3]; 
  219.  
  220.     // 处理剩下的.  
  221.     q = (const int32_t*)p; 
  222.     for(i=0; i<cntRem; ++i) 
  223.     { 
  224.         s += q[i]; 
  225.     } 
  226.  
  227.     return s; 
  228. #endif  // #ifdef INTRIN_SSE2  
  229.  
  230.  
  231.  
  232.  
  233.  
  234. //////////////////////////////////////////////////  
  235. // main  
  236. //////////////////////////////////////////////////  
  237.  
  238. // 变量对齐.  
  239. #ifndef ATTR_ALIGN  
  240. #  if defined(__GNUC__) // GCC  
  241. #    define ATTR_ALIGN(n)   __attribute__((aligned(n)))  
  242. #  else // 否则使用VC格式.  
  243. #    define ATTR_ALIGN(n)   __declspec(align(n))  
  244. #  endif  
  245. #endif  // #ifndef ATTR_ALIGN  
  246.  
  247.  
  248. #define BUFSIZE 4096    // = 32KB{L1 Cache} / (2 * sizeof(int32_t))  
  249. ATTR_ALIGN(32) int32_t buf[BUFSIZE]; 
  250.  
  251. // 测试时的函数类型  
  252. typedef int32_t (*TESTPROC)(const int32_t* pbuf, size_t cntbuf); 
  253.  
  254. // 进行测试  
  255. void runTest(constchar* szname, TESTPROC proc) 
  256.     constint testloop = 4000;  // 重复运算几次延长时间,避免计时精度问题.  
  257.     constclock_t TIMEOUT = CLOCKS_PER_SEC/2;   // 最短测试时间.  
  258.     int i,j,k; 
  259.     clock_t tm0, dt;    // 存储时间.  
  260.     double mps; // M/s.  
  261.     double mps_good = 0;    // 最佳M/s. 因线程切换会导致的数值波动, 于是选取最佳值.  
  262.     volatile int32_t n=0;   // 避免内循环被优化.  
  263.     for(i=1; i<=3; ++i)  // 多次测试.  
  264.     { 
  265.         tm0 = clock(); 
  266.         // main  
  267.         k=0; 
  268.         do 
  269.         { 
  270.             for(j=1; j<=testloop; ++j)   // 重复运算几次延长时间,避免计时开销带来的影响.  
  271.             { 
  272.                 n = proc(buf, BUFSIZE); // 避免内循环被编译优化消掉.  
  273.             } 
  274.             ++k; 
  275.             dt = clock() - tm0; 
  276.         }while(dt<TIMEOUT); 
  277.         // show  
  278.         mps = (double)k*testloop*BUFSIZE*CLOCKS_PER_SEC/(1024.0*1024.0*dt); // k*testloop*BUFSIZE/(1024.0*1024.0) 将数据规模换算为M,然后再乘以 CLOCKS_PER_SEC/dt 换算为M/s .  
  279.         if (mps_good<mps)    mps_good=mps;   // 选取最佳值.  
  280.         //printf("%s:\t%.0f M/s\t//%f\n", szname, mps, n);  
  281.     } 
  282.     printf("%s:\t%.0f M/s\t//%d\n", szname, mps_good, n); 
  283.  
  284. int main(int argc, char* argv[]) 
  285.     char szBuf[64]; 
  286.     int i; 
  287.  
  288.     printf("simdsumint v1.00 (%dbit)\n", INTRIN_WORDSIZE); 
  289.     printf("Compiler: %s\n", COMPILER_NAME); 
  290.     cpu_getbrand(szBuf); 
  291.     printf("CPU:\t%s\n", szBuf); 
  292.     printf("\n"); 
  293.  
  294.     // init buf  
  295.     srand( (unsigned)time( NULL ) ); 
  296.     for (i = 0; i < BUFSIZE; i++) buf[i] = (int32_t)(rand() & 0x7fff);   // 使用&0x7fff是为了使数值在一定范围内,便于观察结果是否正确.  
  297.  
  298.     // test  
  299.     runTest("sumint_base", sumint_base);    // 32位整数数组求和_基本版.  
  300. #ifdef INTRIN_MMX  
  301.     if (simd_mmx(NULL)) 
  302.     { 
  303.         runTest("sumint_mmx", sumint_mmx);  // 32位整数数组求和_MMX版.  
  304.         runTest("sumint_mmx_4loop", sumint_mmx_4loop);  // 32位整数数组求和_MMX四路循环展开版.  
  305.     } 
  306. #endif  // #ifdef INTRIN_MMX  
  307. #ifdef INTRIN_SSE2  
  308.     if (simd_sse_level(NULL) >= SIMD_SSE_2) 
  309.     { 
  310.         runTest("sumint_sse", sumint_sse);  // 32位整数数组求和_SSE版.  
  311.         runTest("sumint_sse_4loop", sumint_sse_4loop);  // 32位整数数组求和_SSE四路循环展开版.  
  312.     } 
  313. #endif  // #ifdef INTRIN_SSE2  
  314.  
  315.     return 0; 
#define __STDC_LIMIT_MACROS	1	// C99整数范围常量. [纯C程序可以不用, 而C++程序必须定义该宏.]

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#include "zintrin.h"
#include "ccpuid.h"


// Compiler name
#define MACTOSTR(x)	#x
#define MACROVALUESTR(x)	MACTOSTR(x)
#if defined(__ICL)	// Intel C++
#  if defined(__VERSION__)
#    define COMPILER_NAME	"Intel C++ " __VERSION__
#  elif defined(__INTEL_COMPILER_BUILD_DATE)
#    define COMPILER_NAME	"Intel C++ (" MACROVALUESTR(__INTEL_COMPILER_BUILD_DATE) ")"
#  else
#    define COMPILER_NAME	"Intel C++"
#  endif	// #  if defined(__VERSION__)
#elif defined(_MSC_VER)	// Microsoft VC++
#  if defined(_MSC_FULL_VER)
#    define COMPILER_NAME	"Microsoft VC++ (" MACROVALUESTR(_MSC_FULL_VER) ")"
#  elif defined(_MSC_VER)
#    define COMPILER_NAME	"Microsoft VC++ (" MACROVALUESTR(_MSC_VER) ")"
#  else
#    define COMPILER_NAME	"Microsoft VC++"
#  endif	// #  if defined(_MSC_FULL_VER)
#elif defined(__GNUC__)	// GCC
#  if defined(__CYGWIN__)
#    define COMPILER_NAME	"GCC(Cygmin) " __VERSION__
#  elif defined(__MINGW32__)
#    define COMPILER_NAME	"GCC(MinGW) " __VERSION__
#  else
#    define COMPILER_NAME	"GCC " __VERSION__
#  endif	// #  if defined(_MSC_FULL_VER)
#else
#  define COMPILER_NAME	"Unknown Compiler"
#endif	// #if defined(__ICL)	// Intel C++


//////////////////////////////////////////////////
// sumint: 32位整数数组求和的函数
//////////////////////////////////////////////////

// 32位整数数组求和_基本版.
//
// result: 返回数组求和结果.
// pbuf: 数组的首地址.
// cntbuf: 数组长度.
int32_t sumint_base(const int32_t* pbuf, size_t cntbuf)
{
	int32_t s = 0;	// 求和变量.
	size_t i;
	for(i=0; i<cntbuf; ++i)
	{
		s += pbuf[i];
	}
	return s;
}

#ifdef INTRIN_MMX
// 32位整数数组求和_MMX版.
int32_t sumint_mmx(const int32_t* pbuf, size_t cntbuf)
{
	int32_t s = 0;	// 求和变量.
	size_t i;
	size_t nBlockWidth = 2;	// 块宽. MMX寄存器能一次处理2个int32_t.
	size_t cntBlock = cntbuf / nBlockWidth;	// 块数.
	size_t cntRem = cntbuf % nBlockWidth;	// 剩余数量.
	__m64 midSum = _mm_setzero_si64();	// 求和变量。[MMX] PXOR, 赋初值0.
	__m64 midLoad;	// 加载.
	const __m64* p = (const __m64*)pbuf;	// MMX批量处理时所用的指针.
	const int32_t* q;	// 单个数据处理时所用指针.

	// MMX批量处理.
	for(i=0; i<cntBlock; ++i)
	{
		midLoad = *p;	// [MMX] MOVQ. 加载.
		midSum = _mm_add_pi32(midSum, midLoad);	// [MMX] PADDD. 32位整数紧缩环绕加法.
		p ++;
	}
	// 合并.
	q = (const int32_t*)&midSum;
	s = q[0] + q[1];

	// 处理剩下的.
	q = (const int32_t*)p;
	for(i=0; i<cntRem; ++i)
	{
		s += q[i];
	}

	// 清理MMX状态.
	_mm_empty();	// [MMX] EMMS.

	return s;
}

// 32位整数数组求和_MMX四路循环展开版.
int32_t sumint_mmx_4loop(const int32_t* pbuf, size_t cntbuf)
{
	int32_t s = 0;	// 返回值.
	size_t i;
	size_t nBlockWidth = 2*4;	// 块宽. MMX寄存器能一次处理2个int32_t,然后循环展开4次.
	size_t cntBlock = cntbuf / nBlockWidth;	// 块数.
	size_t cntRem = cntbuf % nBlockWidth;	// 剩余数量.
	__m64 midSum = _mm_setzero_si64();	// 求和变量。[MMX] PXOR, 赋初值0.
	__m64 midSum1 = _mm_setzero_si64();
	__m64 midSum2 = _mm_setzero_si64();
	__m64 midSum3 = _mm_setzero_si64();
	__m64 midLoad;	// 加载.
	__m64 midLoad1;
	__m64 midLoad2;
	__m64 midLoad3;
	const __m64* p = (const __m64*)pbuf;	// MMX批量处理时所用的指针.
	const int32_t* q;	// 单个数据处理时所用指针.

	// SSE批量处理.
	for(i=0; i<cntBlock; ++i)
	{
		midLoad = *p;	// [MMX] MOVQ. 加载.
		midLoad1 = *(p+1);
		midLoad2 = *(p+2);
		midLoad3 = *(p+3);
		midSum = _mm_add_pi32(midSum, midLoad);	// [MMX] PADDD. 32位整数紧缩环绕加法.
		midSum1 = _mm_add_pi32(midSum1, midLoad1);
		midSum2 = _mm_add_pi32(midSum2, midLoad2);
		midSum3 = _mm_add_pi32(midSum3, midLoad3);
		p += 4;	// 四路循环展开.
	}
	// 合并.
	midSum = _mm_add_pi32(midSum, midSum1);	// 两两合并(0~1).
	midSum2 = _mm_add_pi32(midSum2, midSum3);	// 两两合并(2~3).
	midSum = _mm_add_pi32(midSum, midSum2);	// 两两合并(0~3).
	q = (const int32_t*)&midSum;
	s = q[0] + q[1];

	// 处理剩下的.
	q = (const int32_t*)p;
	for(i=0; i<cntRem; ++i)
	{
		s += q[i];
	}

	// 清理MMX状态.
	_mm_empty();	// [MMX] EMMS.

	return s;
}
#endif	// #ifdef INTRIN_MMX


#ifdef INTRIN_SSE2
// 32位整数数组求和_SSE版.
int32_t sumint_sse(const int32_t* pbuf, size_t cntbuf)
{
	int32_t s = 0;	// 求和变量.
	size_t i;
	size_t nBlockWidth = 4;	// 块宽. SSE寄存器能一次处理4个int32_t.
	size_t cntBlock = cntbuf / nBlockWidth;	// 块数.
	size_t cntRem = cntbuf % nBlockWidth;	// 剩余数量.
	__m128i xidSum = _mm_setzero_si128();	// 求和变量。[SSE2] PXOR. 赋初值0.
	__m128i xidLoad;	// 加载.
	const __m128i* p = (const __m128i*)pbuf;	// SSE批量处理时所用的指针.
	const int32_t* q;	// 单个数据处理时所用指针.

	// SSE批量处理.
	for(i=0; i<cntBlock; ++i)
	{
		xidLoad = _mm_load_si128(p);	// [SSE2] MOVDQA. 加载.
		xidSum = _mm_add_epi32(xidSum, xidLoad);	// [SSE2] PADDD. 32位整数紧缩环绕加法.
		p ++;
	}
	// 合并.
	q = (const int32_t*)&xidSum;
	s = q[0] + q[1] + q[2] + q[3];

	// 处理剩下的.
	q = (const int32_t*)p;
	for(i=0; i<cntRem; ++i)
	{
		s += q[i];
	}

	return s;
}

// 32位整数数组求和_SSE四路循环展开版.
int32_t sumint_sse_4loop(const int32_t* pbuf, size_t cntbuf)
{
	int32_t s = 0;	// 返回值.
	size_t i;
	size_t nBlockWidth = 4*4;	// 块宽. SSE寄存器能一次处理4个int32_t,然后循环展开4次.
	size_t cntBlock = cntbuf / nBlockWidth;	// 块数.
	size_t cntRem = cntbuf % nBlockWidth;	// 剩余数量.
	__m128i xidSum = _mm_setzero_si128();	// 求和变量。[SSE2] PXOR. 赋初值0.
	__m128i xidSum1 = _mm_setzero_si128();
	__m128i xidSum2 = _mm_setzero_si128();
	__m128i xidSum3 = _mm_setzero_si128();
	__m128i xidLoad;	// 加载.
	__m128i xidLoad1;
	__m128i xidLoad2;
	__m128i xidLoad3;
	const __m128i* p = (const __m128i*)pbuf;	// SSE批量处理时所用的指针.
	const int32_t* q;	// 单个数据处理时所用指针.

	// SSE批量处理.
	for(i=0; i<cntBlock; ++i)
	{
		xidLoad = _mm_load_si128(p);	// [SSE2] MOVDQA. 加载.
		xidLoad1 = _mm_load_si128(p+1);
		xidLoad2 = _mm_load_si128(p+2);
		xidLoad3 = _mm_load_si128(p+3);
		xidSum = _mm_add_epi32(xidSum, xidLoad);	// [SSE2] PADDD. 32位整数紧缩环绕加法.
		xidSum1 = _mm_add_epi32(xidSum1, xidLoad1);
		xidSum2 = _mm_add_epi32(xidSum2, xidLoad2);
		xidSum3 = _mm_add_epi32(xidSum3, xidLoad3);
		p += 4;	// 四路循环展开.
	}
	// 合并.
	xidSum = _mm_add_epi32(xidSum, xidSum1);	// 两两合并(0~1).
	xidSum2 = _mm_add_epi32(xidSum2, xidSum3);	// 两两合并(2~3).
	xidSum = _mm_add_epi32(xidSum, xidSum2);	// 两两合并(0~3).
	q = (const int32_t*)&xidSum;
	s = q[0] + q[1] + q[2] + q[3];

	// 处理剩下的.
	q = (const int32_t*)p;
	for(i=0; i<cntRem; ++i)
	{
		s += q[i];
	}

	return s;
}
#endif	// #ifdef INTRIN_SSE2





//////////////////////////////////////////////////
// main
//////////////////////////////////////////////////

// 变量对齐.
#ifndef ATTR_ALIGN
#  if defined(__GNUC__)	// GCC
#    define ATTR_ALIGN(n)	__attribute__((aligned(n)))
#  else	// 否则使用VC格式.
#    define ATTR_ALIGN(n)	__declspec(align(n))
#  endif
#endif	// #ifndef ATTR_ALIGN


#define BUFSIZE	4096	// = 32KB{L1 Cache} / (2 * sizeof(int32_t))
ATTR_ALIGN(32) int32_t buf[BUFSIZE];

// 测试时的函数类型
typedef int32_t (*TESTPROC)(const int32_t* pbuf, size_t cntbuf);

// 进行测试
void runTest(const char* szname, TESTPROC proc)
{
	const int testloop = 4000;	// 重复运算几次延长时间,避免计时精度问题.
	const clock_t TIMEOUT = CLOCKS_PER_SEC/2;	// 最短测试时间.
	int i,j,k;
	clock_t	tm0, dt;	// 存储时间.
	double mps;	// M/s.
	double mps_good = 0;	// 最佳M/s. 因线程切换会导致的数值波动, 于是选取最佳值.
	volatile int32_t n=0;	// 避免内循环被优化.
	for(i=1; i<=3; ++i)	// 多次测试.
	{
		tm0 = clock();
		// main
		k=0;
		do
		{
			for(j=1; j<=testloop; ++j)	// 重复运算几次延长时间,避免计时开销带来的影响.
			{
				n = proc(buf, BUFSIZE);	// 避免内循环被编译优化消掉.
			}
			++k;
			dt = clock() - tm0;
		}while(dt<TIMEOUT);
		// show
		mps = (double)k*testloop*BUFSIZE*CLOCKS_PER_SEC/(1024.0*1024.0*dt);	// k*testloop*BUFSIZE/(1024.0*1024.0) 将数据规模换算为M,然后再乘以 CLOCKS_PER_SEC/dt 换算为M/s .
		if (mps_good<mps)	mps_good=mps;	// 选取最佳值.
		//printf("%s:\t%.0f M/s\t//%f\n", szname, mps, n);
	}
	printf("%s:\t%.0f M/s\t//%d\n", szname, mps_good, n);
}

int main(int argc, char* argv[])
{
	char szBuf[64];
	int i;

	printf("simdsumint v1.00 (%dbit)\n", INTRIN_WORDSIZE);
	printf("Compiler: %s\n", COMPILER_NAME);
	cpu_getbrand(szBuf);
	printf("CPU:\t%s\n", szBuf);
	printf("\n");

	// init buf
	srand( (unsigned)time( NULL ) );
	for (i = 0; i < BUFSIZE; i++) buf[i] = (int32_t)(rand() & 0x7fff);	// 使用&0x7fff是为了使数值在一定范围内,便于观察结果是否正确.

	// test
	runTest("sumint_base", sumint_base);	// 32位整数数组求和_基本版.
#ifdef INTRIN_MMX
	if (simd_mmx(NULL))
	{
		runTest("sumint_mmx", sumint_mmx);	// 32位整数数组求和_MMX版.
		runTest("sumint_mmx_4loop", sumint_mmx_4loop);	// 32位整数数组求和_MMX四路循环展开版.
	}
#endif	// #ifdef INTRIN_MMX
#ifdef INTRIN_SSE2
	if (simd_sse_level(NULL) >= SIMD_SSE_2)
	{
		runTest("sumint_sse", sumint_sse);	// 32位整数数组求和_SSE版.
		runTest("sumint_sse_4loop", sumint_sse_4loop);	// 32位整数数组求和_SSE四路循环展开版.
	}
#endif	// #ifdef INTRIN_SSE2

	return 0;
}

 

2.2 makefile

  全部代码——

  1. # flags 
  2. CC = g++ 
  3. CFS = -Wall -msse2 
  4.  
  5. # args 
  6. RELEASE =0 
  7. BITS = 
  8. CFLAGS = 
  9.  
  10. # [args] 生成模式. 0代表debug模式, 1代表release模式. make RELEASE=1. 
  11. ifeq ($(RELEASE),0) 
  12.     # debug 
  13.     CFS += -g 
  14. else 
  15.     # release 
  16.     CFS += -O3 -DNDEBUG 
  17.     //CFS += -O3 -g -DNDEBUG 
  18. endif 
  19.  
  20. # [args] 程序位数. 32代表32位程序, 64代表64位程序, 其他默认. make BITS=32. 
  21. ifeq ($(BITS),32) 
  22.     CFS += -m32 
  23. else 
  24.     ifeq ($(BITS),64) 
  25.         CFS += -m64 
  26.     else 
  27.     endif 
  28. endif 
  29.  
  30. # [args] 使用 CFLAGS 添加新的参数. make CFLAGS="-mavx". 
  31. CFS += $(CFLAGS) 
  32.  
  33.  
  34. .PHONY : all clean 
  35.  
  36. # files 
  37. TARGETS = simdsumint 
  38. OBJS = simdsumint.o 
  39.  
  40. all : $(TARGETS) 
  41.  
  42. simdsumint : $(OBJS) 
  43.     $(CC) $(CFS) -o $@ $^ 
  44.  
  45.  
  46. simdsumint.o : simdsumint.c zintrin.h ccpuid.h 
  47.     $(CC) $(CFS) -c $< 
  48.  
  49.  
  50. clean : 
  51.     rm -f $(OBJS) $(TARGETS) $(addsuffix .exe,$(TARGETS)) 
# flags
CC = g++
CFS = -Wall -msse2

# args
RELEASE =0
BITS =
CFLAGS =

# [args] 生成模式. 0代表debug模式, 1代表release模式. make RELEASE=1.
ifeq ($(RELEASE),0)
	# debug
	CFS += -g
else
	# release
	CFS += -O3 -DNDEBUG
	//CFS += -O3 -g -DNDEBUG
endif

# [args] 程序位数. 32代表32位程序, 64代表64位程序, 其他默认. make BITS=32.
ifeq ($(BITS),32)
	CFS += -m32
else
	ifeq ($(BITS),64)
		CFS += -m64
	else
	endif
endif

# [args] 使用 CFLAGS 添加新的参数. make CFLAGS="-mavx".
CFS += $(CFLAGS)


.PHONY : all clean

# files
TARGETS = simdsumint
OBJS = simdsumint.o

all : $(TARGETS)

simdsumint : $(OBJS)
	$(CC) $(CFS) -o $@ $^


simdsumint.o : simdsumint.c zintrin.h ccpuid.h
	$(CC) $(CFS) -c $<


clean :
	rm -f $(OBJS) $(TARGETS) $(addsuffix .exe,$(TARGETS))

 

三、编译测试

3.1 编译

  在以下编译器中成功编译—— VC6:x86版。 VC2003:x86版。 VC2005:x86版。 VC2010:x86版、x64版。 GCC 4.7.0(Fedora 17 x64):x86版、x64版。 GCC 4.6.2(MinGW(20120426)):x86版。 GCC 4.7.1(TDM-GCC(MinGW-w64)):x86版、x64版。 llvm-gcc-4.2(Mac OS X Lion 10.7.4, Xcode 4.4.1):x86版、x64版。

3.2 测试

  因虚拟机上的有效率损失,于是仅在真实系统上进行测试。

  系统环境—— CPU:Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz 操作系统:Windows 7 SP1 x64版

  然后分别运行VC与GCC编译的Release版可执行文件,即以下4个程序—— exe\simdsumint_vc32.exe:VC2010 SP1 编译的32位程序,/O2 /arch:SSE2。 exe\simdsumint_vc64.exe:VC2010 SP1 编译的64位程序,/O2 /arch:SSE2。 exe\simdsumint_gcc32.exe:GCC 4.7.1(TDM-GCC(MinGW-w64)) 编译的32位程序,-O3 -mss2。 exe\simdsumint_gcc64.exe:GCC 4.7.1(TDM-GCC(MinGW-w64)) 编译的64位程序,-O3 -mss2。

  测试结果(使用cmdarg_ui)——

 

参考文献—— 《Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes:1, 2A, 2B, 2C, 3A, 3B, and 3C》044US. August 2012. http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html 《Intel® Architecture Instruction Set Extensions Programming Reference》014. AUGUST 2012. http://software.intel.com/en-us/avx/ 《AMD64 Architecture Programmer’s Manual Volume 4: 128-Bit and 256-Bit Media Instructions》. December 2011. http://developer.amd.com/documentation/guides/Pages/default.aspx#manuals 《[C] 让VC、BCB支持C99的整数类型(stdint.h、inttypes.h)(兼容GCC)》. http://www.cnblogs.com/zyl910/archive/2012/08/08/c99int.html 《[C] zintrin.h: 智能引入intrinsic函数 V1.01版。改进对Mac OS X的支持,增加INTRIN_WORDSIZE宏》. http://www.cnblogs.com/zyl910/archive/2012/10/01/zintrin_v101.html 《[C/C++] ccpuid:CPUID信息模块 V1.03版,改进mmx/sse指令可用性检查(使用signal、setjmp,支持纯C)、修正AVX检查Bug》. http://www.cnblogs.com/zyl910/archive/2012/10/13/ccpuid_v103.html 《[x86]SIMD指令集发展历程表(MMX、SSE、AVX等)》. http://www.cnblogs.com/zyl910/archive/2012/02/26/x86_simd_table.html 《SIMD(MMX/SSE/AVX)变量命名规范心得》. http://www.cnblogs.com/zyl910/archive/2012/04/23/simd_var_name.html 《GCC 64位程序的makefile条件编译心得——32位版与64位版、debug版与release版(兼容MinGW、TDM-GCC)》. http://www.cnblogs.com/zyl910/archive/2012/08/14/gcc64_make.html 《[C#] cmdarg_ui:“简单参数命令行程序”的通用图形界面》.  http://www.cnblogs.com/zyl910/archive/2012/06/19/cmdarg_ui.html 《[C] 跨平台使用Intrinsic函数范例1——使用SSE、AVX指令集 处理 单精度浮点数组求和(支持vc、gcc,兼容Windows、Linux、Mac)》. http://www.cnblogs.com/zyl910/archive/2012/10/22/simdsumfloat.html

源码下载—— https://files.cnblogs.com/zyl910/simdsumint.rar

posted @ 2012-12-23 16:26  Goncely  阅读(476)  评论(0)    收藏  举报