跨平台SSE、AVX指令测试
本文面对对SSE等SIMD指令集有一定基础的读者,以单精度浮点数组求和为例演示了如何跨平台使用SSE、AVX指令集。因使用了stdint、zintrin、ccpuid这三个模块,可以完全避免手工编写汇编代码,具有很高可移植性。支持vc、gcc编译器,在Windows、 Linux、Mac这三大平台上成功运行。
一、问题背景
二、范例讲解
2.1 事先准备
这三个模块的纯C版就是一个头文件,用起来很方便,将它们项目中,直接#include就行了。例如——
- #define __STDC_LIMIT_MACROS 1 // C99整数范围常量. [纯C程序可以不用, 而C++程序必须定义该宏.]
- #include "zintrin.h"
- #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语言版
- // 单精度浮点数组求和_基本版.
- //
- // result: 返回数组求和结果.
- // pbuf: 数组的首地址.
- // cntbuf: 数组长度.
- float sumfloat_base(constfloat* pbuf, size_t cntbuf)
- {
- float s = 0; // 求和变量.
- size_t i;
- for(i=0; i<cntbuf; ++i)
- {
- s += pbuf[i];
- }
- return s;
- }
- // 单精度浮点数组求和_基本版.
- //
- // 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普通版
- #ifdef INTRIN_SSE
- // 单精度浮点数组求和_SSE版.
- float sumfloat_sse(constfloat* 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
- #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四路循环展开版
循环展开可以降低循环开销,提高指令级并行性能。
一般来说,四路循环展开就差不多够了。我们可以很方便的将上一节的代码改造为四路循环展开版——
- // 单精度浮点数组求和_SSE四路循环展开版.
- float sumfloat_sse_4loop(constfloat* 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;
- }
- // 单精度浮点数组求和_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普通版
- #ifdef INTRIN_AVX
- // 单精度浮点数组求和_AVX版.
- float sumfloat_avx(constfloat* 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
- #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四路循环展开版.
- float sumfloat_avx_4loop(constfloat* 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;
- }
- // 单精度浮点数组求和_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 测试所用的数组
- // 变量对齐.
- #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];
- // 变量对齐.
- #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 测试函数
- // 进行测试
- void runTest(constchar* 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);
- }
- // 进行测试
- 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数组进行初始化,将数组元素赋随机值——
- // init buf
- srand( (unsigned)time( NULL ) );
- 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类型的有效位数,便于观察结果是否正确.
然后可以开始测试了——
- // 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
- // 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函数中加上——
- 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");
- 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 是一个用来获得编译器名称的宏,它的详细定义是——
- // 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++
- // 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
- #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(constfloat* 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(constfloat* 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(constfloat* 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(constfloat* 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(constfloat* 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)(constfloat* pbuf, size_t cntbuf);
- // 进行测试
- void runTest(constchar* 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;
- }
- #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
- # 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))
- # 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 编译
4.2 测试
系统环境——
CPU:Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz
操作系统:Windows 7 SP1 x64版
源码下载——
http://files.cnblogs.com/zyl910/simdsumfloat.rar
原帖地址:http://blog.csdn.net/zyl910/article/details/8100744
跨平台SSE、AVX指令测试相关推荐
- 单指令多数据SIMD的SSE/AVX指令集和API
https://software.intel.com/sites/landingpage/IntrinsicsGuide/# Technologies MMX SSE SSE2 SSE3 SSSE3 ...
- 一文读懂SIMD指令集 目前最全SSE/AVX介绍
SIMD指令集 SSE/AVX 概述 参考手册 Intel® Intrinsics Guide Tommesani.com Docs Intel® 64 and IA-32 Architectures ...
- SIMD 编程的优势与SIMD指令:SSE/AVX 与编程demo
资源:https://download.csdn.net/download/Rong_Toa/18745608 <Benefits of SIMD Programming | SIMD的优势&g ...
- SIMD(MMX/SSE/AVX)变量命名规范心得
[转载]:SIMD(MMX/SSE/AVX)变量命名规范心得 当使用Intrinsics函数来操作SIMD指令集(MMX/SSE/AVX等)时,会面对不同长度的SIMD数据类型,其中又分为多种紧缩格式 ...
- 深入探讨用位掩码代替分支(8):SSE指令集速度测试
在上一篇测试了MMX指令集,这次我们来测试SSE指令集.说的更精确一点,是测试SSE2指令集. 本篇致力于解决以下问题-- 1.SSE/SSE2指令集是什么? 2.如何阅读Intel/AMD的手册? ...
- SSE AVX 文档
MMX SSE AVX AVX512 所有指令用法说明,运行效率可以在Intel的官网直接查询 https://software.intel.com/sites/landingpage/Intrins ...
- TensorFlow CPU环境 SSE/AVX/FMA 指令集编译
TensorFlow CPU环境 SSE/AVX/FMA 指令集编译 sess.run()出现如下Warning W tensorflow/core/platform/cpu_feature_guar ...
- SSE/AVX指令集学习笔记
因为最近在做SSE/AVX指令集优化视频编码的某些模块,所以要学习SSE指令集的用法.本帖主要记录本人用到的函数的用法. 一.SSE指令(128位寄存器) __m128i _mm_load_si1 ...
- 哪些服务器支持avx指令,至强E5的AVX指令集功能
至强E5的AVX指令集功能 对于Sandy Bridge处理器而言,另一个最大改进是增加了全新的AVX指令集,即高级矢量扩展.至强E5 CPU中,依然沿用了SandyBridge架构的256位指令集. ...
最新文章
- Xdebug的安装-(无错可执行版)
- linux 6.4 安装dns,Linux 轻松上手 架设 CentOS 6.4 DNS+FTP ndash;(六)、安装设定vsftp
- 论手残党画交互原型的正确姿势
- 代金券制作小程序秒代金券_微信小程序制作工具与方法
- 阿里:千亿交易背后的0故障发布
- 解决SecureCRT与SecureFX中文乱码问题
- Python 爬虫进阶必备——某体育网站登录令牌加密分析,赶紧收藏哦!
- 希尔排序(分而治之)
- 基于JAVA+Swing+MYSQL的工资管理系统
- 数据库技大会五周年 技术领袖共聚DTCC
- deal.II链接PETSc过程记录
- php防撞库,基于单片机的倒车防撞预警系统设计和实现
- iphone刷android怎么刷机,不用刷机 让iPhone运行Android 6.0
- 华为云服务器手机密码找回,忘记华为账号密码怎么办?两招就能帮你解决
- 网络视频会议管理系统设计原则
- get 和 post请求的区别
- Ubuntu 解析迅雷链接
- OMNeT++学习---TicToc(1)
- AI机器学习实战の电磁导航智能车中神经网络应用的问题与思考-交流向-本科生竞赛
- 急需大量网银和电子商务经验人才
热门文章
- 一些c++的常见问题(系列一)
- php实现用户注册代码,php实现用户注册的代码示例
- linux中GIT组件,GitLab在Linux下安装
- mysql+怎样查询最后几条_mysql 查询开头或最后几行
- java中load_java之Properties集合中的方法load
- 硬中断 / 软中断的原理和实现
- python open encoding为无效的参数_TypeError:“encoding”是无效的关键字参数ex23.py
- 让 Netty “榨干” 你的CPU!
- 一做就是一天,这一天天的谁受得了
- 请查收你的GitHub 2020 年度报告!