并行编程之跨平台使用SSE、AVX指令集心得——以单精度浮点数组求和为例(支持vc、gcc,兼容Windows、Linux、Mac)

服务端实时推送技术之SSE(Server-Send Events) 服务端推送,也称为消息推送或通知推送,是一种允许应用服务器主动将信息发送到客户端的能力,为客户端提供了实时的信息更新和通知,增强了用户体验。 阅读详情

 

作者:zyl910。

  本文面对对SSE等SIMD指令集有一定基础的读者,以单精度浮点数组求和为例演示了如何跨平台使用SSE、AVX指令集。因使用了stdint、zintrin、ccpuid这三个模块,可以完全避免手工编写汇编代码,具有很高可移植性。支持vc、gcc编译器,在Windows、Linux、Mac这三大平台上成功运行。


一、问题背景

  最初,我们只能使用汇编语言来编写SIMD代码。不仅写起来很麻烦,而且易读性、可维护性、移植性都较差。
  不久,VC、GCC等编译器相继支持了Intrinsic函数,使我们可以摆脱汇编,利用C语言来调用SIMD指令集,大大提高了易读性和可维护。而且移植性也有提高,能在同一编译器上实现32位与64位的平滑过渡。
  但当代码在另一种编译器编译时,会遇到一些问题而无法编译。甚至在使用同一种编译器的不同版本时,也会遇到无法编译问题。

  首先是整数类型问题——
  传统C语言的short、int、long等整数类型是与平台相关的,不同平台上的位长是不同的(例如Windows是LLP64模型,Linux、Mac等Unix系统多采用LP64模型)。而使用SSE等SIMD指令集时需要精确计算数据的位数,不同位长的数据必须使用不同的指令来处理。
  有一个解决办法,就是使用C99标准中stdint.h所提供的指定位长的整数类型。GCC对C99标准支持性较好,而VC的步骤很慢,貌似直到VC2010才支持stdint.h。而很多时候我们为了兼容旧代码,不得不使用VC等老版本的VC编译器。

  其次是Intrinsic函数的头文件问题,不同编译器所使用的头文件不同——
  对于早期版本VC,需要根据具体的指令集需求,手动引入mmintrin.h、xmmintrin.h等头文件。对于VC2005或更高版本,引入intrin.h就行了,它会自动引入当前编译器所支持的所有Intrinsic头文件。
  对于早期版本GCC,也是手动引入mmintrin.h、xmmintrin.h等头文件。而对于高版本的GCC,引入x86intrin.h就行了,它会自动引入当前编译环境所允许的Intrinsic头文件。

  再次是当前编译环境下的Intrinsic函数集支持性问题——
  对于VC来说,VC6支持MMX、3DNow!、SSE、SSE2,然后更高版本的VC支持更多的指令集。但是,VC没有提供检测Intrinsic函数集支持性的办法。例如你在VC2010上编写了一段使用了AVX Intrinsic函数的代码,但拿到VC2005上就不能通过编译了。其次,VC不支持64位下的MMX,这让一些老程序迁徙到64位版时遭来了一些麻烦。
  而对于GCC来说,它使用-mmmx、-msse等编译器开关来启用各种指令集,同时定义了对应的 __MMX__、__SSE__等宏,然后x86intrin.h会根据这些宏来声明相应的Intrinsic函数集。__MMX__、__SSE__等宏可以帮助我们判断Intrinsic函数集是否支持,但这只是GCC的专用功能。
  此外还有一些细节问题,例如某些Intrinsic函数仅在64下才能使用、有些老版本编译器的头文件缺少某个Intrinsic函数。所以我们希望有一种统一的方式来判断Intrinsic函数集的支持性。

  除了编译期间的问题外,还有运行期间的问题——
  在运行时,怎么检测当前处理器支持哪些指令集?
  虽然X86体系提供了用来检测处理器的CPUID指令,但它没有规范的Intrinsic函数,在不同的编译器上的用法不同。
  而且X86体系有很多种指令集,每种指令集具体的检测方法是略有区别的。尤其是SSE、AVX这样的SIMD指令集是需要操作系统配合才能正常使用的,所以在CPUID检查通过后,还需要进一步验证。


二、范例讲解

2.1 事先准备

  为了解决上面提到的问题,我编写了三个模块——
stdint:智能支持C99的stdint.h,解决整数类型问题。最新版的地址是 http://www.cnblogs.com/zyl910/archive/2012/08/08/c99int.html 。
zintrin:在编译时检测Intrinsic函数集支持性,并自动引入相关头文件、修正细节问题。最新版的地址是 http://www.cnblogs.com/zyl910/archive/2012/10/01/zintrin_v101.html 。
ccpuid:在编译时检测指令集的支持性。最新版的地址是 http://www.cnblogs.com/zyl910/archive/2012/10/13/ccpuid_v103.html 。

  这三个模块的纯C版就是一个头文件,用起来很方便,将它们项目中,直接#include就行了。例如——

  1. #define __STDC_LIMIT_MACROS 1   // C99整数范围常量. [纯C程序可以不用, 而C++程序必须定义该宏.] 
  2.  
  3. #include "zintrin.h" 
  4. #include "ccpuid.h" 
#define __STDC_LIMIT_MACROS	1	// C99整数范围常量. [纯C程序可以不用, 而C++程序必须定义该宏.]

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


  因为stdint.h会被zintrin.h或ccpuid.h引用,所以不需要手动引入它。
  因为它们用到了C99整数范围常量,所以应该在程序的最前面定义__STDC_LIMIT_MACROS宏(或者可以在项目配置、编译器命令行等位置进行配置)。根据C99规范,纯C程序可以不用, 而C++程序必须定义该宏。本文为了演示,定义了该宏。


2.2 C语言版

  我们先用C语言编写一个基本的单精度浮点数组求和函数——

  1. // 单精度浮点数组求和_基本版. 
  2. // 
  3. // result: 返回数组求和结果. 
  4. // pbuf: 数组的首地址. 
  5. // cntbuf: 数组长度. 
  6. float sumfloat_base(const float* pbuf, size_t cntbuf) 
  7. { 
  8.     float s = 0;    // 求和变量. 
  9.     size_t i; 
  10.     for(i=0; i<cntbuf; ++i) 
  11.     { 
  12.         s += pbuf[i]; 
  13.     } 
  14.     return s; 
  15. } 
// 单精度浮点数组求和_基本版.
//
// result: 返回数组求和结果.
// pbuf: 数组的首地址.
// cntbuf: 数组长度.
float sumfloat_base(const float* pbuf, size_t cntbuf)
{
	float s = 0;	// 求和变量.
	size_t i;
	for(i=0; i<cntbuf; ++i)
	{
		s += pbuf[i];
	}
	return s;
}


  该函数很容易理解——先将返回值赋初值0,然后循环加上数组中每一项的值。


2.3 SSE版

2.3.1 SSE普通版

  SSE寄存器是128位的,对应__m128类型,它能一次能处理4个单精度浮点数。
  很多SSE指令要求内存地址按16字节对齐。本文为了简化,假定浮点数组的首地址是总是16字节对齐的,仅需要考虑数组长度不是4的整数倍问题。
  因使用了SSE Intrinsic函数,我们可以根据zintrin.h所提供的INTRIN_SSE宏进行条件编译。
  代码如下——

  1. #ifdef INTRIN_SSE 
  2. // 单精度浮点数组求和_SSE版. 
  3. float sumfloat_sse(const float* pbuf, size_t cntbuf) 
  4. { 
  5.     float s = 0;    // 求和变量. 
  6.     size_t i; 
  7.     size_t nBlockWidth = 4; // 块宽. SSE寄存器能一次处理4个float. 
  8.     size_t cntBlock = cntbuf / nBlockWidth; // 块数. 
  9.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量. 
  10.     __m128 xfsSum = _mm_setzero_ps();   // 求和变量。[SSE] 赋初值0 
  11.     __m128 xfsLoad; // 加载. 
  12.     const float* p = pbuf;  // SSE批量处理时所用的指针. 
  13.     const float* q; // 将SSE变量上的多个数值合并时所用指针. 
  14.  
  15.     // SSE批量处理. 
  16.     for(i=0; i<cntBlock; ++i) 
  17.     { 
  18.         xfsLoad = _mm_load_ps(p);   // [SSE] 加载 
  19.         xfsSum = _mm_add_ps(xfsSum, xfsLoad);   // [SSE] 单精浮点紧缩加法 
  20.         p += nBlockWidth; 
  21.     } 
  22.     // 合并. 
  23.     q = (const float*)&xfsSum; 
  24.     s = q[0] + q[1] + q[2] + q[3]; 
  25.  
  26.     // 处理剩下的. 
  27.     for(i=0; i<cntRem; ++i) 
  28.     { 
  29.         s += p[i]; 
  30.     } 
  31.  
  32.     return s; 
  33. } 
  34.  
  35. #endif  // #ifdef INTRIN_SSE 
#ifdef INTRIN_SSE
// 单精度浮点数组求和_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;
}

#endif	// #ifdef INTRIN_SSE


  上述代码大致可分为四个部分——
1. 变量定义与初始化。
2. SSE批量处理。即对前面能凑成4个一组的数据,利用SSE的128位宽度同时对4个数累加。
3. 合并。将__m128上的多个数值合并到求和变量。因考虑某些编译器不能直接使用“.”来访问__m128变量中的数据,于是利用指针q来访问xfsSum中的数据。
4. 处理剩下的。即对尾部不能凑成4个一组的数据,采用基本的逐项相加算法。

  上述代码总共用到了3个SSE Intrinsic函数——
_mm_setzero_ps:对应XORPS指令。将__m128上的每一个单精度浮点数均赋0值,伪代码:for(i=0;i<4;++i) C[i]=0.0f。
_mm_load_ps:对应MOVPS指令。从内存中对齐加载4个单精度浮点数到__m128变量,伪代码:for(i=0;i<4;++i) C[i]=_A[i]。
_mm_add_ps:对应ADDPS指令。相加,即对2个__m128变量的4个单精度浮点数进行垂直相加,伪代码:for(i=0;i<4;++i) C[i]=A[i]+B[i]。


2.3.2 SSE四路循环展开版

  循环展开可以降低循环开销,提高指令级并行性能。
  一般来说,四路循环展开就差不多够了。我们可以很方便的将上一节的代码改造为四路循环展开版——

  1. // 单精度浮点数组求和_SSE四路循环展开版. 
  2. float sumfloat_sse_4loop(const float* pbuf, size_t cntbuf) 
  3. { 
  4.     float s = 0;    // 返回值. 
  5.     size_t i; 
  6.     size_t nBlockWidth = 4*4;   // 块宽. SSE寄存器能一次处理4个float,然后循环展开4次. 
  7.     size_t cntBlock = cntbuf / nBlockWidth; // 块数. 
  8.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量. 
  9.     __m128 xfsSum = _mm_setzero_ps();   // 求和变量。[SSE] 赋初值0 
  10.     __m128 xfsSum1 = _mm_setzero_ps(); 
  11.     __m128 xfsSum2 = _mm_setzero_ps(); 
  12.     __m128 xfsSum3 = _mm_setzero_ps(); 
  13.     __m128 xfsLoad; // 加载. 
  14.     __m128 xfsLoad1; 
  15.     __m128 xfsLoad2; 
  16.     __m128 xfsLoad3; 
  17.     const float* p = pbuf;  // SSE批量处理时所用的指针. 
  18.     const float* q; // 将SSE变量上的多个数值合并时所用指针. 
  19.  
  20.     // SSE批量处理. 
  21.     for(i=0; i<cntBlock; ++i) 
  22.     { 
  23.         xfsLoad = _mm_load_ps(p);   // [SSE] 加载. 
  24.         xfsLoad1 = _mm_load_ps(p+4); 
  25.         xfsLoad2 = _mm_load_ps(p+8); 
  26.         xfsLoad3 = _mm_load_ps(p+12); 
  27.         xfsSum = _mm_add_ps(xfsSum, xfsLoad);   // [SSE] 单精浮点紧缩加法 
  28.         xfsSum1 = _mm_add_ps(xfsSum1, xfsLoad1); 
  29.         xfsSum2 = _mm_add_ps(xfsSum2, xfsLoad2); 
  30.         xfsSum3 = _mm_add_ps(xfsSum3, xfsLoad3); 
  31.         p += nBlockWidth; 
  32.     } 
  33.     // 合并. 
  34.     xfsSum = _mm_add_ps(xfsSum, xfsSum1);   // 两两合并(0~1). 
  35.     xfsSum2 = _mm_add_ps(xfsSum2, xfsSum3); // 两两合并(2~3). 
  36.     xfsSum = _mm_add_ps(xfsSum, xfsSum2);   // 两两合并(0~3). 
  37.     q = (const float*)&xfsSum; 
  38.     s = q[0] + q[1] + q[2] + q[3]; 
  39.  
  40.     // 处理剩下的. 
  41.     for(i=0; i<cntRem; ++i) 
  42.     { 
  43.         s += p[i]; 
  44.     } 
  45.  
  46.     return s; 
  47. } 
// 单精度浮点数组求和_SSE四路循环展开版.
float sumfloat_sse_4loop(const float* pbuf, size_t cntbuf)
{
	float s = 0;	// 返回值.
	size_t i;
	size_t nBlockWidth = 4*4;	// 块宽. SSE寄存器能一次处理4个float,然后循环展开4次.
	size_t cntBlock = cntbuf / nBlockWidth;	// 块数.
	size_t cntRem = cntbuf % nBlockWidth;	// 剩余数量.
	__m128 xfsSum = _mm_setzero_ps();	// 求和变量。[SSE] 赋初值0
	__m128 xfsSum1 = _mm_setzero_ps();
	__m128 xfsSum2 = _mm_setzero_ps();
	__m128 xfsSum3 = _mm_setzero_ps();
	__m128 xfsLoad;	// 加载.
	__m128 xfsLoad1;
	__m128 xfsLoad2;
	__m128 xfsLoad3;
	const float* p = pbuf;	// SSE批量处理时所用的指针.
	const float* q;	// 将SSE变量上的多个数值合并时所用指针.

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

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

	return s;
}



2.4 AVX版

2.4.1 AVX普通版

  AVX寄存器是256位的,对应__m256类型,它能一次能处理8个单精度浮点数。
  很多AVX指令要求内存地址按32字节对齐。本文为了简化,假定浮点数组的首地址是总是32字节对齐的,仅需要考虑数组长度不是8的整数倍问题。
  因使用了AVX Intrinsic函数,我们可以根据zintrin.h所提供的INTRIN_AVX宏进行条件编译。

  代码如下——

  1. #ifdef INTRIN_AVX 
  2. // 单精度浮点数组求和_AVX版. 
  3. float sumfloat_avx(const float* pbuf, size_t cntbuf) 
  4. { 
  5.     float s = 0;    // 求和变量. 
  6.     size_t i; 
  7.     size_t nBlockWidth = 8; // 块宽. AVX寄存器能一次处理8个float. 
  8.     size_t cntBlock = cntbuf / nBlockWidth; // 块数. 
  9.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量. 
  10.     __m256 yfsSum = _mm256_setzero_ps();    // 求和变量。[AVX] 赋初值0 
  11.     __m256 yfsLoad; // 加载. 
  12.     const float* p = pbuf;  // AVX批量处理时所用的指针. 
  13.     const float* q; // 将AVX变量上的多个数值合并时所用指针. 
  14.  
  15.     // AVX批量处理. 
  16.     for(i=0; i<cntBlock; ++i) 
  17.     { 
  18.         yfsLoad = _mm256_load_ps(p);    // [AVX] 加载 
  19.         yfsSum = _mm256_add_ps(yfsSum, yfsLoad);    // [AVX] 单精浮点紧缩加法 
  20.         p += nBlockWidth; 
  21.     } 
  22.     // 合并. 
  23.     q = (const float*)&yfsSum; 
  24.     s = q[0] + q[1] + q[2] + q[3] + q[4] + q[5] + q[6] + q[7]; 
  25.  
  26.     // 处理剩下的. 
  27.     for(i=0; i<cntRem; ++i) 
  28.     { 
  29.         s += p[i]; 
  30.     } 
  31.  
  32.     return s; 
  33. } 
  34.  
  35. #endif  // #ifdef INTRIN_AVX 
#ifdef INTRIN_AVX
// 单精度浮点数组求和_AVX版.
float sumfloat_avx(const float* pbuf, size_t cntbuf)
{
	float s = 0;	// 求和变量.
	size_t i;
	size_t nBlockWidth = 8;	// 块宽. AVX寄存器能一次处理8个float.
	size_t cntBlock = cntbuf / nBlockWidth;	// 块数.
	size_t cntRem = cntbuf % nBlockWidth;	// 剩余数量.
	__m256 yfsSum = _mm256_setzero_ps();	// 求和变量。[AVX] 赋初值0
	__m256 yfsLoad;	// 加载.
	const float* p = pbuf;	// AVX批量处理时所用的指针.
	const float* q;	// 将AVX变量上的多个数值合并时所用指针.

	// AVX批量处理.
	for(i=0; i<cntBlock; ++i)
	{
		yfsLoad = _mm256_load_ps(p);	// [AVX] 加载
		yfsSum = _mm256_add_ps(yfsSum, yfsLoad);	// [AVX] 单精浮点紧缩加法
		p += nBlockWidth;
	}
	// 合并.
	q = (const float*)&yfsSum;
	s = q[0] + q[1] + q[2] + q[3] + q[4] + q[5] + q[6] + q[7];

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

	return s;
}

#endif	// #ifdef INTRIN_AVX


  由上可见,将SSE Intrinsic代码(sumfloat_sse)升级为 AVX Intrinsic代码(sumfloat_avx)是很容易的——
1. 升级数据类型,将__m128升级成了__m256。
2. 升级Intrinsic函数,在函数名中加入255。例如_mm_setzero_ps、_mm_load_ps、_mm_add_ps,对应的AVX版函数是 _mm256_setzero_ps、_mm256_load_ps、_mm256_add_ps。
3. 因位宽翻倍,地址计算与数据合并的代码需稍加改动。

  当使用VC2010编译含有AVX的代码时,VC会提醒你——
warning C4752: 发现 Intel(R) 高级矢量扩展;请考虑使用 /arch:AVX

  目前“/arch:AVX”尚未整合到项目属性的“C++\代码生成\启用增强指令集”中,需要手动在项目属性的“C++\命令行”的附加选项中加上“/arch:AVX”——

详见MSDN——
http://msdn.microsoft.com/zh-cn/library/7t5yh4fd(v=vs.100).aspx
在 Visual Studio 中设置 /arch:AVX 编译器选项
1.打开项目的“属性页”对话框。 有关更多信息,请参见 如何:打开项目属性页。
2.单击“C/C++”文件夹。
3.单击“命令行”属性页。
4.在“附加选项”框中添加 /arch:AVX。


2.4.2 AVX四路循环展开版

  同样的,我们可以编写AVX四路循环展开版——

  1. // 单精度浮点数组求和_AVX四路循环展开版. 
  2. float sumfloat_avx_4loop(const float* pbuf, size_t cntbuf) 
  3. { 
  4.     float s = 0;    // 求和变量. 
  5.     size_t i; 
  6.     size_t nBlockWidth = 8*4;   // 块宽. AVX寄存器能一次处理8个float,然后循环展开4次. 
  7.     size_t cntBlock = cntbuf / nBlockWidth; // 块数. 
  8.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量. 
  9.     __m256 yfsSum = _mm256_setzero_ps();    // 求和变量。[AVX] 赋初值0 
  10.     __m256 yfsSum1 = _mm256_setzero_ps(); 
  11.     __m256 yfsSum2 = _mm256_setzero_ps(); 
  12.     __m256 yfsSum3 = _mm256_setzero_ps(); 
  13.     __m256 yfsLoad; // 加载. 
  14.     __m256 yfsLoad1; 
  15.     __m256 yfsLoad2; 
  16.     __m256 yfsLoad3; 
  17.     const float* p = pbuf;  // AVX批量处理时所用的指针. 
  18.     const float* q; // 将AVX变量上的多个数值合并时所用指针. 
  19.  
  20.     // AVX批量处理. 
  21.     for(i=0; i<cntBlock; ++i) 
  22.     { 
  23.         yfsLoad = _mm256_load_ps(p);    // [AVX] 加载. 
  24.         yfsLoad1 = _mm256_load_ps(p+8); 
  25.         yfsLoad2 = _mm256_load_ps(p+16); 
  26.         yfsLoad3 = _mm256_load_ps(p+24); 
  27.         yfsSum = _mm256_add_ps(yfsSum, yfsLoad);    // [AVX] 单精浮点紧缩加法 
  28.         yfsSum1 = _mm256_add_ps(yfsSum1, yfsLoad1); 
  29.         yfsSum2 = _mm256_add_ps(yfsSum2, yfsLoad2); 
  30.         yfsSum3 = _mm256_add_ps(yfsSum3, yfsLoad3); 
  31.         p += nBlockWidth; 
  32.     } 
  33.     // 合并. 
  34.     yfsSum = _mm256_add_ps(yfsSum, yfsSum1);    // 两两合并(0~1). 
  35.     yfsSum2 = _mm256_add_ps(yfsSum2, yfsSum3);  // 两两合并(2~3). 
  36.     yfsSum = _mm256_add_ps(yfsSum, yfsSum2);    // 两两合并(0~3). 
  37.     q = (const float*)&yfsSum; 
  38.     s = q[0] + q[1] + q[2] + q[3] + q[4] + q[5] + q[6] + q[7]; 
  39.  
  40.     // 处理剩下的. 
  41.     for(i=0; i<cntRem; ++i) 
  42.     { 
  43.         s += p[i]; 
  44.     } 
  45.  
  46.     return s; 
  47. } 
// 单精度浮点数组求和_AVX四路循环展开版.
float sumfloat_avx_4loop(const float* pbuf, size_t cntbuf)
{
	float s = 0;	// 求和变量.
	size_t i;
	size_t nBlockWidth = 8*4;	// 块宽. AVX寄存器能一次处理8个float,然后循环展开4次.
	size_t cntBlock = cntbuf / nBlockWidth;	// 块数.
	size_t cntRem = cntbuf % nBlockWidth;	// 剩余数量.
	__m256 yfsSum = _mm256_setzero_ps();	// 求和变量。[AVX] 赋初值0
	__m256 yfsSum1 = _mm256_setzero_ps();
	__m256 yfsSum2 = _mm256_setzero_ps();
	__m256 yfsSum3 = _mm256_setzero_ps();
	__m256 yfsLoad;	// 加载.
	__m256 yfsLoad1;
	__m256 yfsLoad2;
	__m256 yfsLoad3;
	const float* p = pbuf;	// AVX批量处理时所用的指针.
	const float* q;	// 将AVX变量上的多个数值合并时所用指针.

	// AVX批量处理.
	for(i=0; i<cntBlock; ++i)
	{
		yfsLoad = _mm256_load_ps(p);	// [AVX] 加载.
		yfsLoad1 = _mm256_load_ps(p+8);
		yfsLoad2 = _mm256_load_ps(p+16);
		yfsLoad3 = _mm256_load_ps(p+24);
		yfsSum = _mm256_add_ps(yfsSum, yfsLoad);	// [AVX] 单精浮点紧缩加法
		yfsSum1 = _mm256_add_ps(yfsSum1, yfsLoad1);
		yfsSum2 = _mm256_add_ps(yfsSum2, yfsLoad2);
		yfsSum3 = _mm256_add_ps(yfsSum3, yfsLoad3);
		p += nBlockWidth;
	}
	// 合并.
	yfsSum = _mm256_add_ps(yfsSum, yfsSum1);	// 两两合并(0~1).
	yfsSum2 = _mm256_add_ps(yfsSum2, yfsSum3);	// 两两合并(2~3).
	yfsSum = _mm256_add_ps(yfsSum, yfsSum2);	// 两两合并(0~3).
	q = (const float*)&yfsSum;
	s = q[0] + q[1] + q[2] + q[3] + q[4] + q[5] + q[6] + q[7];

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

	return s;
}


2.5 测试框架

2.5.1 测试所用的数组

  首先考虑一下测试所用的数组的长度应该是多少比较好。
  为了避免内存带宽问题,这个数组最好能放在L1 Data Cache中。现在的处理器的L1 Data Cache一般是32KB,为了保险最好再除以2,那么数组的长度应该是 32KB/(2*sizeof(float))=4096。
  其次考虑内存对齐问题,avx要求32字节对齐。我们可以定义一个ATTR_ALIGN宏来统一处理变量的内存对齐问题。
  该数组定义如下——

  1. // 变量对齐. 
  2. #ifndef ATTR_ALIGN 
  3. #  if defined(__GNUC__) // GCC 
  4. #    define ATTR_ALIGN(n)   __attribute__((aligned(n))) 
  5. #  else // 否则使用VC格式. 
  6. #    define ATTR_ALIGN(n)   __declspec(align(n)) 
  7. #  endif 
  8. #endif  // #ifndef ATTR_ALIGN 
  9.  
  10.  
  11. #define BUFSIZE 4096    // = 32KB{L1 Cache} / (2 * sizeof(float)) 
  12. ATTR_ALIGN(32) float buf[BUFSIZE]; 
// 变量对齐.
#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(float))
ATTR_ALIGN(32) float buf[BUFSIZE];



2.5.2 测试函数

  如果为每一个函数都编写一套测试代码,那不仅代码量大,而且不易维护。
  可以考虑利用函数指针来实现一套测试框架。
  因sumfloat_base等函数的签名是一致的,于是可以定义这样的一种函数指针——
// 测试时的函数类型
typedef float (*TESTPROC)(const float* pbuf, size_t cntbuf);

  然后再编写一个对TESTPROC函数指针进行测试的函数——

  1. // 进行测试 
  2. void runTest(const char* szname, TESTPROC proc) 
  3. { 
  4.     const int testloop = 4000;  // 重复运算几次延长时间,避免计时精度问题. 
  5.     const clock_t TIMEOUT = CLOCKS_PER_SEC/2;   // 最短测试时间. 
  6.     int i,j,k; 
  7.     clock_t tm0, dt;    // 存储时间. 
  8.     double mps; // M/s. 
  9.     double mps_good = 0;    // 最佳M/s. 因线程切换会导致的数值波动, 于是选取最佳值. 
  10.     volatile float n=0; // 避免内循环被优化. 
  11.     for(i=1; i<=3; ++i)  // 多次测试. 
  12.     { 
  13.         tm0 = clock(); 
  14.         // main 
  15.         k=0; 
  16.         do 
  17.         { 
  18.             for(j=1; j<=testloop; ++j)   // 重复运算几次延长时间,避免计时开销带来的影响. 
  19.             { 
  20.                 n = proc(buf, BUFSIZE); // 避免内循环被编译优化消掉. 
  21.             } 
  22.             ++k; 
  23.             dt = clock() - tm0; 
  24.         }while(dt<TIMEOUT); 
  25.         // show 
  26.         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 . 
  27.         if (mps_good<mps)    mps_good=mps;   // 选取最佳值. 
  28.         //printf("%s:\t%.0f M/s\t//%f\n", szname, mps, n); 
  29.     } 
  30.     printf("%s:\t%.0f M/s\t//%f\n", szname, mps_good, n); 
  31. } 
// 进行测试
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 float 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//%f\n", szname, mps_good, n);
}


  j是最内层的循环,负责多次调用TESTPROC函数指针。如果每调用一次TESTPROC函数指针后又调用clock函数,那会带来较大的计时开销,影响评测成绩。
  k循环负责检测超时。当发现超过预定时限,便计算mps,即每秒钟处理了多少百万个单精度浮点数。然后存储最佳的mps。
  i是最外层循环的循环变量,循环3次然后报告最佳值。


2.5.3 进行测试

  在进行测试之前,需要对buf数组进行初始化,将数组元素赋随机值——

  1. // init buf 
  2. srand( (unsigned)time( NULL ) ); 
  3. for (i = 0; i < BUFSIZE; i++) buf[i] = (float)(rand() & 0x3f);   // 使用&0x3f是为了让求和后的数值不会超过float类型的有效位数,便于观察结果是否正确. 
	// init buf
	srand( (unsigned)time( NULL ) );
	for (i = 0; i < BUFSIZE; i++) buf[i] = (float)(rand() & 0x3f);	// 使用&0x3f是为了让求和后的数值不会超过float类型的有效位数,便于观察结果是否正确.


  然后可以开始测试了——

  1.     // test 
  2.     runTest("sumfloat_base", sumfloat_base);    // 单精度浮点数组求和_基本版. 
  3. #ifdef INTRIN_SSE 
  4.     if (simd_sse_level(NULL) >= SIMD_SSE_1) 
  5.     { 
  6.         runTest("sumfloat_sse", sumfloat_sse);  // 单精度浮点数组求和_SSE版. 
  7.         runTest("sumfloat_sse_4loop", sumfloat_sse_4loop);  // 单精度浮点数组求和_SSE四路循环展开版. 
  8.     } 
  9. #endif  // #ifdef INTRIN_SSE 
  10. #ifdef INTRIN_AVX 
  11.     if (simd_avx_level(NULL) >= SIMD_AVX_1) 
  12.     { 
  13.         runTest("sumfloat_avx", sumfloat_avx);  // 单精度浮点数组求和_SSE版. 
  14.         runTest("sumfloat_avx_4loop", sumfloat_avx_4loop);  // 单精度浮点数组求和_SSE四路循环展开版. 
  15.     } 
  16. #endif  // #ifdef INTRIN_AVX 
	// test
	runTest("sumfloat_base", sumfloat_base);	// 单精度浮点数组求和_基本版.
#ifdef INTRIN_SSE
	if (simd_sse_level(NULL) >= SIMD_SSE_1)
	{
		runTest("sumfloat_sse", sumfloat_sse);	// 单精度浮点数组求和_SSE版.
		runTest("sumfloat_sse_4loop", sumfloat_sse_4loop);	// 单精度浮点数组求和_SSE四路循环展开版.
	}
#endif	// #ifdef INTRIN_SSE
#ifdef INTRIN_AVX
	if (simd_avx_level(NULL) >= SIMD_AVX_1)
	{
		runTest("sumfloat_avx", sumfloat_avx);	// 单精度浮点数组求和_SSE版.
		runTest("sumfloat_avx_4loop", sumfloat_avx_4loop);	// 单精度浮点数组求和_SSE四路循环展开版.
	}
#endif	// #ifdef INTRIN_AVX



2.6 杂项

  为了方便对比测试,可以在程序启动时显示程序版本、编译器名称、CPU型号信息。即在main函数中加上——

  1. char szBuf[64]; 
  2. int i; 
  3.  
  4. printf("simdsumfloat v1.00 (%dbit)\n", INTRIN_WORDSIZE); 
  5. printf("Compiler: %s\n", COMPILER_NAME); 
  6. cpu_getbrand(szBuf); 
  7. printf("CPU:\t%s\n", szBuf); 
  8. printf("\n"); 
	char szBuf[64];
	int i;

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


  INTRIN_WORDSIZE 宏是 zintrin.h 提供的,为当前机器的字长。
  cpu_getbrand是 ccpuid.h 提供的,用于获得CPU型号字符串。
  COMPILER_NAME 是一个用来获得编译器名称的宏,它的详细定义是——

  1. // Compiler name 
  2. #define MACTOSTR(x) #x 
  3. #define MACROVALUESTR(x)    MACTOSTR(x) 
  4. #if defined(__ICL)  // Intel C++ 
  5. #  if defined(__VERSION__) 
  6. #    define COMPILER_NAME   "Intel C++ " __VERSION__ 
  7. #  elif defined(__INTEL_COMPILER_BUILD_DATE) 
  8. #    define COMPILER_NAME   "Intel C++ (" MACROVALUESTR(__INTEL_COMPILER_BUILD_DATE) ")" 
  9. #  else 
  10. #    define COMPILER_NAME   "Intel C++" 
  11. #  endif    // #  if defined(__VERSION__) 
  12. #elif defined(_MSC_VER) // Microsoft VC++ 
  13. #  if defined(_MSC_FULL_VER) 
  14. #    define COMPILER_NAME   "Microsoft VC++ (" MACROVALUESTR(_MSC_FULL_VER) ")" 
  15. #  elif defined(_MSC_VER) 
  16. #    define COMPILER_NAME   "Microsoft VC++ (" MACROVALUESTR(_MSC_VER) ")" 
  17. #  else 
  18. #    define COMPILER_NAME   "Microsoft VC++" 
  19. #  endif    // #  if defined(_MSC_FULL_VER) 
  20. #elif defined(__GNUC__) // GCC 
  21. #  if defined(__CYGWIN__) 
  22. #    define COMPILER_NAME   "GCC(Cygmin) " __VERSION__ 
  23. #  elif defined(__MINGW32__) 
  24. #    define COMPILER_NAME   "GCC(MinGW) " __VERSION__ 
  25. #  else 
  26. #    define COMPILER_NAME   "GCC " __VERSION__ 
  27. #  endif    // #  if defined(_MSC_FULL_VER) 
  28. #else 
  29. #  define COMPILER_NAME "Unknown Compiler" 
  30. #endif  // #if defined(__ICL)   // Intel C++ 
// 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++


三、全部代码

3.1 simdsumfloat.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. // sumfloat: 单精度浮点数组求和的函数 
  45. ////////////////////////////////////////////////// 
  46.  
  47. // 单精度浮点数组求和_基本版. 
  48. // 
  49. // result: 返回数组求和结果. 
  50. // pbuf: 数组的首地址. 
  51. // cntbuf: 数组长度. 
  52. float sumfloat_base(const float* pbuf, size_t cntbuf) 
  53. { 
  54.     float s = 0;    // 求和变量. 
  55.     size_t i; 
  56.     for(i=0; i<cntbuf; ++i) 
  57.     { 
  58.         s += pbuf[i]; 
  59.     } 
  60.     return s; 
  61. } 
  62.  
  63. #ifdef INTRIN_SSE 
  64. // 单精度浮点数组求和_SSE版. 
  65. float sumfloat_sse(const float* pbuf, size_t cntbuf) 
  66. { 
  67.     float s = 0;    // 求和变量. 
  68.     size_t i; 
  69.     size_t nBlockWidth = 4; // 块宽. SSE寄存器能一次处理4个float. 
  70.     size_t cntBlock = cntbuf / nBlockWidth; // 块数. 
  71.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量. 
  72.     __m128 xfsSum = _mm_setzero_ps();   // 求和变量。[SSE] 赋初值0 
  73.     __m128 xfsLoad; // 加载. 
  74.     const float* p = pbuf;  // SSE批量处理时所用的指针. 
  75.     const float* q; // 将SSE变量上的多个数值合并时所用指针. 
  76.  
  77.     // SSE批量处理. 
  78.     for(i=0; i<cntBlock; ++i) 
  79.     { 
  80.         xfsLoad = _mm_load_ps(p);   // [SSE] 加载 
  81.         xfsSum = _mm_add_ps(xfsSum, xfsLoad);   // [SSE] 单精浮点紧缩加法 
  82.         p += nBlockWidth; 
  83.     } 
  84.     // 合并. 
  85.     q = (const float*)&xfsSum; 
  86.     s = q[0] + q[1] + q[2] + q[3]; 
  87.  
  88.     // 处理剩下的. 
  89.     for(i=0; i<cntRem; ++i) 
  90.     { 
  91.         s += p[i]; 
  92.     } 
  93.  
  94.     return s; 
  95. } 
  96.  
  97. // 单精度浮点数组求和_SSE四路循环展开版. 
  98. float sumfloat_sse_4loop(const float* pbuf, size_t cntbuf) 
  99. { 
  100.     float s = 0;    // 返回值. 
  101.     size_t i; 
  102.     size_t nBlockWidth = 4*4;   // 块宽. SSE寄存器能一次处理4个float,然后循环展开4次. 
  103.     size_t cntBlock = cntbuf / nBlockWidth; // 块数. 
  104.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量. 
  105.     __m128 xfsSum = _mm_setzero_ps();   // 求和变量。[SSE] 赋初值0 
  106.     __m128 xfsSum1 = _mm_setzero_ps(); 
  107.     __m128 xfsSum2 = _mm_setzero_ps(); 
  108.     __m128 xfsSum3 = _mm_setzero_ps(); 
  109.     __m128 xfsLoad; // 加载. 
  110.     __m128 xfsLoad1; 
  111.     __m128 xfsLoad2; 
  112.     __m128 xfsLoad3; 
  113.     const float* p = pbuf;  // SSE批量处理时所用的指针. 
  114.     const float* q; // 将SSE变量上的多个数值合并时所用指针. 
  115.  
  116.     // SSE批量处理. 
  117.     for(i=0; i<cntBlock; ++i) 
  118.     { 
  119.         xfsLoad = _mm_load_ps(p);   // [SSE] 加载. 
  120.         xfsLoad1 = _mm_load_ps(p+4); 
  121.         xfsLoad2 = _mm_load_ps(p+8); 
  122.         xfsLoad3 = _mm_load_ps(p+12); 
  123.         xfsSum = _mm_add_ps(xfsSum, xfsLoad);   // [SSE] 单精浮点紧缩加法 
  124.         xfsSum1 = _mm_add_ps(xfsSum1, xfsLoad1); 
  125.         xfsSum2 = _mm_add_ps(xfsSum2, xfsLoad2); 
  126.         xfsSum3 = _mm_add_ps(xfsSum3, xfsLoad3); 
  127.         p += nBlockWidth; 
  128.     } 
  129.     // 合并. 
  130.     xfsSum = _mm_add_ps(xfsSum, xfsSum1);   // 两两合并(0~1). 
  131.     xfsSum2 = _mm_add_ps(xfsSum2, xfsSum3); // 两两合并(2~3). 
  132.     xfsSum = _mm_add_ps(xfsSum, xfsSum2);   // 两两合并(0~3). 
  133.     q = (const float*)&xfsSum; 
  134.     s = q[0] + q[1] + q[2] + q[3]; 
  135.  
  136.     // 处理剩下的. 
  137.     for(i=0; i<cntRem; ++i) 
  138.     { 
  139.         s += p[i]; 
  140.     } 
  141.  
  142.     return s; 
  143. } 
  144. #endif  // #ifdef INTRIN_SSE 
  145.  
  146.  
  147. #ifdef INTRIN_AVX 
  148. // 单精度浮点数组求和_AVX版. 
  149. float sumfloat_avx(const float* pbuf, size_t cntbuf) 
  150. { 
  151.     float s = 0;    // 求和变量. 
  152.     size_t i; 
  153.     size_t nBlockWidth = 8; // 块宽. AVX寄存器能一次处理8个float. 
  154.     size_t cntBlock = cntbuf / nBlockWidth; // 块数. 
  155.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量. 
  156.     __m256 yfsSum = _mm256_setzero_ps();    // 求和变量。[AVX] 赋初值0 
  157.     __m256 yfsLoad; // 加载. 
  158.     const float* p = pbuf;  // AVX批量处理时所用的指针. 
  159.     const float* q; // 将AVX变量上的多个数值合并时所用指针. 
  160.  
  161.     // AVX批量处理. 
  162.     for(i=0; i<cntBlock; ++i) 
  163.     { 
  164.         yfsLoad = _mm256_load_ps(p);    // [AVX] 加载 
  165.         yfsSum = _mm256_add_ps(yfsSum, yfsLoad);    // [AVX] 单精浮点紧缩加法 
  166.         p += nBlockWidth; 
  167.     } 
  168.     // 合并. 
  169.     q = (const float*)&yfsSum; 
  170.     s = q[0] + q[1] + q[2] + q[3] + q[4] + q[5] + q[6] + q[7]; 
  171.  
  172.     // 处理剩下的. 
  173.     for(i=0; i<cntRem; ++i) 
  174.     { 
  175.         s += p[i]; 
  176.     } 
  177.  
  178.     return s; 
  179. } 
  180.  
  181. // 单精度浮点数组求和_AVX四路循环展开版. 
  182. float sumfloat_avx_4loop(const float* pbuf, size_t cntbuf) 
  183. { 
  184.     float s = 0;    // 求和变量. 
  185.     size_t i; 
  186.     size_t nBlockWidth = 8*4;   // 块宽. AVX寄存器能一次处理8个float,然后循环展开4次. 
  187.     size_t cntBlock = cntbuf / nBlockWidth; // 块数. 
  188.     size_t cntRem = cntbuf % nBlockWidth;   // 剩余数量. 
  189.     __m256 yfsSum = _mm256_setzero_ps();    // 求和变量。[AVX] 赋初值0 
  190.     __m256 yfsSum1 = _mm256_setzero_ps(); 
  191.     __m256 yfsSum2 = _mm256_setzero_ps(); 
  192.     __m256 yfsSum3 = _mm256_setzero_ps(); 
  193.     __m256 yfsLoad; // 加载. 
  194.     __m256 yfsLoad1; 
  195.     __m256 yfsLoad2; 
  196.     __m256 yfsLoad3; 
  197.     const float* p = pbuf;  // AVX批量处理时所用的指针. 
  198.     const float* q; // 将AVX变量上的多个数值合并时所用指针. 
  199.  
  200.     // AVX批量处理. 
  201.     for(i=0; i<cntBlock; ++i) 
  202.     { 
  203.         yfsLoad = _mm256_load_ps(p);    // [AVX] 加载. 
  204.         yfsLoad1 = _mm256_load_ps(p+8); 
  205.         yfsLoad2 = _mm256_load_ps(p+16); 
  206.         yfsLoad3 = _mm256_load_ps(p+24); 
  207.         yfsSum = _mm256_add_ps(yfsSum, yfsLoad);    // [AVX] 单精浮点紧缩加法 
  208.         yfsSum1 = _mm256_add_ps(yfsSum1, yfsLoad1); 
  209.         yfsSum2 = _mm256_add_ps(yfsSum2, yfsLoad2); 
  210.         yfsSum3 = _mm256_add_ps(yfsSum3, yfsLoad3); 
  211.         p += nBlockWidth; 
  212.     } 
  213.     // 合并. 
  214.     yfsSum = _mm256_add_ps(yfsSum, yfsSum1);    // 两两合并(0~1). 
  215.     yfsSum2 = _mm256_add_ps(yfsSum2, yfsSum3);  // 两两合并(2~3). 
  216.     yfsSum = _mm256_add_ps(yfsSum, yfsSum2);    // 两两合并(0~3). 
  217.     q = (const float*)&yfsSum; 
  218.     s = q[0] + q[1] + q[2] + q[3] + q[4] + q[5] + q[6] + q[7]; 
  219.  
  220.     // 处理剩下的. 
  221.     for(i=0; i<cntRem; ++i) 
  222.     { 
  223.         s += p[i]; 
  224.     } 
  225.  
  226.     return s; 
  227. } 
  228.  
  229. #endif  // #ifdef INTRIN_AVX 
  230.  
  231.  
  232.  
  233. ////////////////////////////////////////////////// 
  234. // main 
  235. ////////////////////////////////////////////////// 
  236.  
  237. // 变量对齐. 
  238. #ifndef ATTR_ALIGN 
  239. #  if defined(__GNUC__) // GCC 
  240. #    define ATTR_ALIGN(n)   __attribute__((aligned(n))) 
  241. #  else // 否则使用VC格式. 
  242. #    define ATTR_ALIGN(n)   __declspec(align(n)) 
  243. #  endif 
  244. #endif  // #ifndef ATTR_ALIGN 
  245.  
  246.  
  247. #define BUFSIZE 4096    // = 32KB{L1 Cache} / (2 * sizeof(float)) 
  248. ATTR_ALIGN(32) float buf[BUFSIZE]; 
  249.  
  250. // 测试时的函数类型 
  251. typedef float (*TESTPROC)(const float* pbuf, size_t cntbuf); 
  252.  
  253. // 进行测试 
  254. void runTest(const char* szname, TESTPROC proc) 
  255. { 
  256.     const int testloop = 4000;  // 重复运算几次延长时间,避免计时精度问题. 
  257.     const clock_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 float 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//%f\n", szname, mps_good, n); 
  283. } 
  284.  
  285. int main(int argc, char* argv[]) 
  286. { 
  287.     char szBuf[64]; 
  288.     int i; 
  289.  
  290.     printf("simdsumfloat v1.00 (%dbit)\n", INTRIN_WORDSIZE); 
  291.     printf("Compiler: %s\n", COMPILER_NAME); 
  292.     cpu_getbrand(szBuf); 
  293.     printf("CPU:\t%s\n", szBuf); 
  294.     printf("\n"); 
  295.  
  296.     // init buf 
  297.     srand( (unsigned)time( NULL ) ); 
  298.     for (i = 0; i < BUFSIZE; i++) buf[i] = (float)(rand() & 0x3f);   // 使用&0x3f是为了让求和后的数值不会超过float类型的有效位数,便于观察结果是否正确. 
  299.  
  300.     // test 
  301.     runTest("sumfloat_base", sumfloat_base);    // 单精度浮点数组求和_基本版. 
  302. #ifdef INTRIN_SSE 
  303.     if (simd_sse_level(NULL) >= SIMD_SSE_1) 
  304.     { 
  305.         runTest("sumfloat_sse", sumfloat_sse);  // 单精度浮点数组求和_SSE版. 
  306.         runTest("sumfloat_sse_4loop", sumfloat_sse_4loop);  // 单精度浮点数组求和_SSE四路循环展开版. 
  307.     } 
  308. #endif  // #ifdef INTRIN_SSE 
  309. #ifdef INTRIN_AVX 
  310.     if (simd_avx_level(NULL) >= SIMD_AVX_1) 
  311.     { 
  312.         runTest("sumfloat_avx", sumfloat_avx);  // 单精度浮点数组求和_AVX版. 
  313.         runTest("sumfloat_avx_4loop", sumfloat_avx_4loop);  // 单精度浮点数组求和_AVX四路循环展开版. 
  314.     } 
  315. #endif  // #ifdef INTRIN_AVX 
  316.  
  317.     return 0; 
  318. } 
#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++


//////////////////////////////////////////////////
// sumfloat: 单精度浮点数组求和的函数
//////////////////////////////////////////////////

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

#ifdef INTRIN_SSE
// 单精度浮点数组求和_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;
}

// 单精度浮点数组求和_SSE四路循环展开版.
float sumfloat_sse_4loop(const float* pbuf, size_t cntbuf)
{
	float s = 0;	// 返回值.
	size_t i;
	size_t nBlockWidth = 4*4;	// 块宽. SSE寄存器能一次处理4个float,然后循环展开4次.
	size_t cntBlock = cntbuf / nBlockWidth;	// 块数.
	size_t cntRem = cntbuf % nBlockWidth;	// 剩余数量.
	__m128 xfsSum = _mm_setzero_ps();	// 求和变量。[SSE] 赋初值0
	__m128 xfsSum1 = _mm_setzero_ps();
	__m128 xfsSum2 = _mm_setzero_ps();
	__m128 xfsSum3 = _mm_setzero_ps();
	__m128 xfsLoad;	// 加载.
	__m128 xfsLoad1;
	__m128 xfsLoad2;
	__m128 xfsLoad3;
	const float* p = pbuf;	// SSE批量处理时所用的指针.
	const float* q;	// 将SSE变量上的多个数值合并时所用指针.

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

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

	return s;
}
#endif	// #ifdef INTRIN_SSE


#ifdef INTRIN_AVX
// 单精度浮点数组求和_AVX版.
float sumfloat_avx(const float* pbuf, size_t cntbuf)
{
	float s = 0;	// 求和变量.
	size_t i;
	size_t nBlockWidth = 8;	// 块宽. AVX寄存器能一次处理8个float.
	size_t cntBlock = cntbuf / nBlockWidth;	// 块数.
	size_t cntRem = cntbuf % nBlockWidth;	// 剩余数量.
	__m256 yfsSum = _mm256_setzero_ps();	// 求和变量。[AVX] 赋初值0
	__m256 yfsLoad;	// 加载.
	const float* p = pbuf;	// AVX批量处理时所用的指针.
	const float* q;	// 将AVX变量上的多个数值合并时所用指针.

	// AVX批量处理.
	for(i=0; i<cntBlock; ++i)
	{
		yfsLoad = _mm256_load_ps(p);	// [AVX] 加载
		yfsSum = _mm256_add_ps(yfsSum, yfsLoad);	// [AVX] 单精浮点紧缩加法
		p += nBlockWidth;
	}
	// 合并.
	q = (const float*)&yfsSum;
	s = q[0] + q[1] + q[2] + q[3] + q[4] + q[5] + q[6] + q[7];

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

	return s;
}

// 单精度浮点数组求和_AVX四路循环展开版.
float sumfloat_avx_4loop(const float* pbuf, size_t cntbuf)
{
	float s = 0;	// 求和变量.
	size_t i;
	size_t nBlockWidth = 8*4;	// 块宽. AVX寄存器能一次处理8个float,然后循环展开4次.
	size_t cntBlock = cntbuf / nBlockWidth;	// 块数.
	size_t cntRem = cntbuf % nBlockWidth;	// 剩余数量.
	__m256 yfsSum = _mm256_setzero_ps();	// 求和变量。[AVX] 赋初值0
	__m256 yfsSum1 = _mm256_setzero_ps();
	__m256 yfsSum2 = _mm256_setzero_ps();
	__m256 yfsSum3 = _mm256_setzero_ps();
	__m256 yfsLoad;	// 加载.
	__m256 yfsLoad1;
	__m256 yfsLoad2;
	__m256 yfsLoad3;
	const float* p = pbuf;	// AVX批量处理时所用的指针.
	const float* q;	// 将AVX变量上的多个数值合并时所用指针.

	// AVX批量处理.
	for(i=0; i<cntBlock; ++i)
	{
		yfsLoad = _mm256_load_ps(p);	// [AVX] 加载.
		yfsLoad1 = _mm256_load_ps(p+8);
		yfsLoad2 = _mm256_load_ps(p+16);
		yfsLoad3 = _mm256_load_ps(p+24);
		yfsSum = _mm256_add_ps(yfsSum, yfsLoad);	// [AVX] 单精浮点紧缩加法
		yfsSum1 = _mm256_add_ps(yfsSum1, yfsLoad1);
		yfsSum2 = _mm256_add_ps(yfsSum2, yfsLoad2);
		yfsSum3 = _mm256_add_ps(yfsSum3, yfsLoad3);
		p += nBlockWidth;
	}
	// 合并.
	yfsSum = _mm256_add_ps(yfsSum, yfsSum1);	// 两两合并(0~1).
	yfsSum2 = _mm256_add_ps(yfsSum2, yfsSum3);	// 两两合并(2~3).
	yfsSum = _mm256_add_ps(yfsSum, yfsSum2);	// 两两合并(0~3).
	q = (const float*)&yfsSum;
	s = q[0] + q[1] + q[2] + q[3] + q[4] + q[5] + q[6] + q[7];

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

	return s;
}

#endif	// #ifdef INTRIN_AVX



//////////////////////////////////////////////////
// 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(float))
ATTR_ALIGN(32) float buf[BUFSIZE];

// 测试时的函数类型
typedef float (*TESTPROC)(const float* 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 float 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//%f\n", szname, mps_good, n);
}

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

	printf("simdsumfloat 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] = (float)(rand() & 0x3f);	// 使用&0x3f是为了让求和后的数值不会超过float类型的有效位数,便于观察结果是否正确.

	// test
	runTest("sumfloat_base", sumfloat_base);	// 单精度浮点数组求和_基本版.
#ifdef INTRIN_SSE
	if (simd_sse_level(NULL) >= SIMD_SSE_1)
	{
		runTest("sumfloat_sse", sumfloat_sse);	// 单精度浮点数组求和_SSE版.
		runTest("sumfloat_sse_4loop", sumfloat_sse_4loop);	// 单精度浮点数组求和_SSE四路循环展开版.
	}
#endif	// #ifdef INTRIN_SSE
#ifdef INTRIN_AVX
	if (simd_avx_level(NULL) >= SIMD_AVX_1)
	{
		runTest("sumfloat_avx", sumfloat_avx);	// 单精度浮点数组求和_AVX版.
		runTest("sumfloat_avx_4loop", sumfloat_avx_4loop);	// 单精度浮点数组求和_AVX四路循环展开版.
	}
#endif	// #ifdef INTRIN_AVX

	return 0;
}



3.2 makefile

  全部代码——

  1. # flags 
  2. CC = g++ 
  3. CFS = -Wall -msse 
  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 = simdsumfloat 
  38. OBJS = simdsumfloat.o 
  39.  
  40. all : $(TARGETS) 
  41.  
  42. simdsumfloat : $(OBJS) 
  43.     $(CC) $(CFS) -o $@ $^ 
  44.  
  45.  
  46. simdsumfloat.o : simdsumfloat.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 -msse

# 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 = simdsumfloat
OBJS = simdsumfloat.o

all : $(TARGETS)

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


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


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



四、编译测试

4.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版。



4.2 测试

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

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

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

  测试结果(使用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


源码下载——
http://files.cnblogs.com/zyl910/simdsumfloat.rar

 

 

原帖地址:http://blog.youkuaiyun.com/zyl910/article/details/8100744

WebSocket 和SSE的区别以及优缺点 WebSocket 和 Server-Sent Events(SSE)都是用于:WebSocket 是全双工的,这意味着服务器和客户端可以同时发送和接收消息。而 SSE 是单向的,只允许服务器发送消息到客户端。:WebSocket 可以发送任何类型的数据,包括文本和二进制数据。而 SSE 只能发送文本数据。:WebSocket 连接一旦建立,就会保持打开状态,直到客户端或服务器主动关闭它。而 SSE 连接在每次消息发送完毕后都会被关闭,然后客户端需要重新连接。 阅读详情

相关推荐

C++avx256指令集加速实例

背景 avx256是x86cpu架构下实现SIMD(单指令多数据)的指令集。它能够利用cpu内部256bit的寄存器,同时对4位double或8位int类型的数操作,达到很好的加速效果。这里通过一个计算π\piπ的实例来展示其威力: π=4∫0111+x2dx \pi = 4\int_0^1\frac{1}{1+x^2}dx π=4∫01​1+x21​dx linux系统下可以通过如下命令查看电脑是否支持avx256指令集: cat /proc/cpuinfo | grep flags sse4就表示

http://www.icfgblog.com/ 7693

SSE(Server-Sent Events)技术及其简单演示

SSE(Server-Sent Events)是一种用于实现服务器主动向客户端推送数据的技术,也被称为“事件流”(Event Stream)。它基于 HTTP协议,利用了其长连接特性,在客户端与服务器之间建立一条持久化连接,并通过这条连接实现服务器向客户端的实时数据推送。

qq_61887118的博客 3万+

Alibaba Java Coding Guidelines,以后我的Java代码规范,就靠它了

前言 对于Java代码规范,业界有统一的标准,不少公司对此都有一定的要求。但是即便如此,庞大的Java使用者由于经验很水平的限制,未必有规范编码的意识,而且即便经验丰富的老Java 程序员也无法做到时刻将规范牢记于心。所以对于代码规范扫描工具,一经问世就广受青睐,阿里巴巴出品的Alibaba Java Coding Guidelines(阿里巴巴Java代码规约扫描,以下简称为AJCG)插件便是其...

java梦工厂 857

[C] 跨平台使用Intrinsic函数范例1——使用SSE、AVX指令集 处理 单精度浮点数组求和(支持vc、gcc,兼容Windows、Linux、Mac)...

作者:zyl910。   本文面对对SSE等SIMD指令集有一定基础的读者,以单精度浮点数组求和为例演示了如何跨平台使用SSE、AVX指令集。因使用了stdint、zintrin、ccpuid这三个模块,可以完全避免手工编写汇编代码,具有很高可移植性。支持vc、gcc编译器,在Windows、Linux、Mac这三大平台上成功运行。 一、问题背景   最初,我们只能使用汇编语言来编写SIMD...

weixin_33971205的博客 1433

CTF 命令执行绕过总结

tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示。或:rce-xor-or.php & rce-xor-or.py。# ls -t >shell 将输出输入到shell文件中。异或:rce-xor.php & rce-xor.py。%09(url传递)(cat%09flag.php)sh /flag 2>%261 //报错出文件内容。2、1使用空变量$*和$@,$x,${x}绕过。7、异或无符号(过滤0-9a-zA-Z)more:一页一页的显示档案内容。

jnszstmei的博客 1958

版电力系统故障录波_故障录波识图与典型故障波形分析

1、设置故障录波的目的故障录波装置是对电力系统运行状况进行监控的一种自动化设备。系统正常运行时,录波装置不启动录波,当系统发生故障时,通过启动录波装置迅速启动录波器录波,直接记录反应故障装置安装处系统故障的电气量。2、故障录波的作用(1)正确评价继电保护和自动装置的工作。(2)正确分析事故的原因并研究防止对侧。(3)发现继电保护和自动装置缺陷。(4)发现一次设备缺陷,及时消除隐患。(5)帮助寻找故...

weixin_42495661的博客 1万+

并行编程之跨平台使用SSE、AVX指令集心得——以单精度浮点数组求和为例(支持vc、gcc,兼容Windows、Linux、Mac)...

作者:zyl910。   本文面对对SSE等SIMD指令集有一定基础的读者,以单精度浮点数组求和为例演示了如何跨平台使用SSE、AVX指令集。因使用了stdint、zintrin、ccpuid这三个模块,可以完全避免手工编写汇编代码,具有很高可移植性。支持vc、gcc编译器,在Windows、Linux、Mac这三大平台上成功运行。 一、问题背景   最初,我们只能使用...

weixin_30548917的博客 398

o型圈沟槽设计_O型密封圈——详细选型过程及步骤

一、密封圈常用材料二、密封圈常用密封形式密封圈分为活塞密封,活塞杆密封,端面密封,旋转密封。三、O型密封圈的选型规则O型密封圈的尺寸图径向安装O型圈选型(静态和动态运动)活塞杆密封选择适当规格的O形圈时,应该保证其外径(d1+2*d2)至少等于或者大于沟槽外径d6;当选择的O形圈直径d1大于250 mm时, O形圈外径将不能超过沟槽外径的3%;当选择的O形圈直径d1小于250mm时, O形圈外径将...

weixin_39856630的博客 2万+

跨平台使用Intrinsic函数范例1——使用SSE、AVX指令集 处理 单精度浮点数组求和(支持vc、gcc,兼容Windows、Linux、Mac)

作者:zyl910。   本文面对对SSE等SIMD指令集有一定基础的读者,以单精度浮点数组求和为例演示了如何跨平台使用SSE、AVX指令集。因使用了stdint、zintrin、ccpuid这三个模块,可以完全避免手工编写汇编代码,具有很高可移植性。支持vc、gcc编译器,在Windows、Linux、Mac这三大平台上成功运行。 一、问题背景   最初,我们只能使用汇编语言来编写SI

ceasar11的专栏 1898

SpringBoot使用mybatis-plus的自动代码生成器

一.SpringBoot使用mybatis-plus+自动代码生成 使用mybatis-plus可以自动帮我们生成通用的 controller,service,dao,mapper 二.加入依赖 <!-- MP 核心库 --> <dependency> <groupId>com.baomidou</groupId> <artif...

农民娃儿的博客 2261

从零实现Unitree Go2的SLAM建图与导航:手把手集成slam_toolbox和nav2(附Rviz2可视化配置)

本文详细指导如何在Unitree Go2四足机器人上集成slam_toolbox和nav2,实现SLAM建图与导航功能。从ROS2环境配置、雷达数据处理到slam_toolbox参数优化,再到nav2路径规划与Rviz2可视化调试,提供完整解决方案。特别适合机器人开发者快速掌握ROS2导航栈在Unitree Go2上的应用实践。

weixin_33737134的博客 1229

[C] 跨平台使用Intrinsic函数范例1——使用SSE、AVX指令集 处理 单精度浮点数组求和(支持vc、gcc,兼容Windows、Linux、Mac)

作者:zyl910。   本文面对对SSE等SIMD指令集有一定基础的读者,以单精度浮点数组求和为例演示了如何跨平台使用SSE、AVX指令集。因使用了stdint、zintrin、ccpuid这三个模块,可以完全避免手工编写汇编代码,具有很高可移植性。支持vc、gcc编译器,在Windows、Linux、Mac这三大平台上成功运行。 一、问题背景   最初,我们只能使用汇编语言来编写SI

骑驴看唱本 1713

什么是 SAP PCE?

SAP Private Cloud Edition 是一种基于云计算的部署模式,它结合了公有云的灵活性和私有云的安全性,为企业提供了量身定制的云服务。在 PCE 环境中,SAP 托管和维护客户的应用系统(如 SAP S/4HANA、SAP SuccessFactors 或 SAP BW/4HANA),同时客户拥有更高的定制化权限。专属环境:每个客户在云中拥有独立的资源,这保障了更高的安全性和性能。全面托管:SAP 提供基础架构、系统管理和更新服务,降低了客户的运维成本。灵活扩展。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 2241

一个简单求和函数的C-》SSE-》AVX的实现过程

(转)Intrinsic—使用SSE、AVX指令集处理单精度浮点数组求和(支持vc、gcc,兼容Win、Linux、Mac)   (2013-01-14 10:45:16) 转载▼ 标签:  杂谈 分类: 汇编 [C] 跨平台使用Intrinsic函数范例1——使用SSE、AVX指令集 处理 单精度浮点数组求和(支持vc、gcc

战斗蜗牛的专栏 6312

基于投影法实现扫描文件的光学字符分割——Matlab实现

投影法是指将原图像沿着某个特定的方向进行投影,得到投影线上的像素点的个数或像素值总和,从而可以得到该方向上物体的轮廓信息。对于字符分割,我们可以将图像进行水平和垂直两个方向的投影,从而得到字符之间的分割线。在OCR技术中,字符分割是非常重要的一步骤,因为字符之间的分割对字符识别的准确性有很大的影响。通过对投影值的判断,可以求出字符之间的分割线,这里我们根据垂直方向上的投影值进行判断,选取一个阈值进行筛选即可。使用sum()函数分别计算出水平和垂直方向上的投影值,得到每一行和每一列的投影值。

TechGlide的博客 581

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

[C] 跨平台使用Intrinsic函数范例2——使用SSE2、AVX指令集 处理 双精度浮点数组求和

作者:zyl910。   本文面对对SSE等SIMD指令集有一定基础的读者,以双精度浮点数组求和为例演示了如何跨平台使用SSE2、AVX指令集。支持vc、gcc编译器,在Windows、Linux、Mac这三大平台上成功运行。 一、关键讲解   前文(http://www.cnblogs.com/zyl910/archive/2012/10/22/simdsumfloat.html)演示

zyl910的专栏 6085

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

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

作者:zyl910。   本文面对对SSE等SIMD指令集有一定基础的读者,以32位整数数组求和为例演示了如何跨平台使用MMX、SSE2指令集。支持vc、gcc编译器,在Windows、Linux、Mac这三大平台上成功运行。   一、关键讲解   前文(http://www.cnblogs.com/zyl910/archive/2012/10/22/simdsumfloat.html)演

zyl910的专栏 1万+

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

[C] 跨平台使用Intrinsic函数范例2——使用SSE2、AVX指令集 处理 双精度浮点数组求和...

作者:zyl910。   本文面对对SSE等SIMD指令集有一定基础的读者,以双精度浮点数组求和为例演示了如何跨平台使用SSE2、AVX指令集。支持vc、gcc编译器,在Windows、Linux、Mac这三大平台上成功运行。 一、关键讲解   前文(http://www.cnblogs.com/zyl910/archive/2012/10/22/simdsumfloat.html)演示了如...

weixin_33895475的博客 428

[C] zintrin.h : 智能引入intrinsic函数。支持VC、GCC,兼容Windows、Linux、Mac OS X

作者:zyl910。   现在很多编译器支持intrinsic函数,这给编写SSE等SIMD代码带来了方便。但是各个编译器略有差异,于是我编写了zintrin.h,智能引入intrinsic函数。 一、各种编译器的区别 1.1 Visual C++(Windows)   最早支持intrinsic函数的VC编译器是VC 6.0。它在装上Visual Studio 6.0 Service

zyl910的专栏 7225
上一篇: 文艺程序员的好妹子--google总裁
下一篇: 75道逻辑思维题
crazyhacking
博客等级 码龄15年 299粉丝 148原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值