SSE(Streaming SIMD Extensions)指令是一种SIMD 指令, Intrinsics函数则是对SSE指令的函数封装,利用C语言形式来调用SIMD指令集,大大提高了易读性和可维护。Intrinsics函数的使用可查看手册Intel Intrinsics Guide

关于本文实现了单精度浮点数组的求和,切实感受SSE带来的速度提升。本文代码主要来自[1].

首先是不使用任何加速手段的求和函数:

//普通版
float sumfloat_base(const float *pbuf,unsigned int cntbuf)
{
float s=;
for (unsigned int i=;i<cntbuf;i++)
{
s+=pbuf[i];
}
return s;
}

在程序优化中有一种经常使用的方法:循环展开。循环展开可以降低循环开销,提高指令级并行性能。此处使用四路展开(测试表明,更多路展开难以带来更快的速度):

//四路展开
float sumfloat_base_4loop(const float*pbuf,unsigned int cntbuf)
{
float s=;
float fSum0=,fSum1=,fSum2=,fSum3=;
unsigned int i=;
const float *p=pbuf;
for (;i<=cntbuf-;i+=)//cntbuf-4不会每次都计算,编译器实现会计算好循环次数!
{
fSum0+=p[i];
fSum1+=p[i+];
fSum2+=p[i+];
fSum3+=p[i+];
}
s=fSum0+fSum1+fSum2+fSum3;
for (;i<cntbuf;i++)
{
s+=p[i];
}
return s;
}

接着使用SSE 进行加速,由于SSE寄存器位宽128,因此一次能处理4个float类型的数据。SSE指令要求内存地址按16字节对齐,因此在声明缓冲区时使用了__declspec(align(16))。对于动态申请的内存可使用_aligned_malloc 。

//SSE版
float sumfloat_sse(const float *pbuf ,unsigned int cntbuf)
{
float s=;
int nBlockWidth=;//SSE一次处理4个float
int cntBlock=cntbuf/nBlockWidth;
int cntRem=cntbuf%nBlockWidth;
__m128 fSum=_mm_setzero_ps();//求和变量,初值清零
__m128 fLoad;
const float*p=pbuf;
for (unsigned int i=;i<cntBlock;i++)
{
fLoad=_mm_load_ps(p);//加载
fSum=_mm_add_ps(fSum,fLoad);//求和
p+=nBlockWidth;
}
const float *q=(const float*)&fSum;
s=q[]+q[]+q[]+q[]; //合并
for (int i=;i<cntRem;i++)//处理尾部剩余数据
{
s+=p[i];
}
return s;
}

本程序中使用的Intrinsics函数为:

__m128 _mm_load_ps (float const* mem_addr):

从16字节对齐的内存mem_addr中加载128位(4个单精度浮点数)到寄存器。对应指令 movaps xmm, m128

__m128 _mm_setzero_ps (void):返回一个_m128类型的全零向量。对应指令:xorps xmm, xmm

__m128 _mm_add_ps (__m128 a, __m128 b):将4对32位浮点数同时进行相加操作。这4对32位浮点数来自两个128位的存储单元,再把计算结果(相加之和)赋给一个128位的存储单元。对应指令:addps xmm, xmm

void _mm_store_ps (float* mem_addr, __m128 a):将128位数据存入16字节对齐的内存中。对应指令:movaps m128, xmm

最后在SSE版本中再次使用循环展开:

//SSE+四路展开
float sumfloat_sse_4loop(const float *pbuf,unsigned int cntbuf)
{
float s=;
unsigned int nBlockWidth=*;
unsigned int cntBlock=cntbuf/nBlockWidth;
unsigned int cntRem=cntbuf%nBlockWidth;
__m128 fSum0=_mm_setzero_ps();//求和变量,初值清零
__m128 fSum1=_mm_setzero_ps();
__m128 fSum2=_mm_setzero_ps();
__m128 fSum3=_mm_setzero_ps();
__m128 fLoad0,fLoad1,fLoad2,fLoad3;
const float *p=pbuf;
for (unsigned int i=;i<cntBlock;i++)
{
fLoad0=_mm_load_ps(p);//加载
fLoad1=_mm_load_ps(p+);
fLoad2=_mm_load_ps(p+);
fLoad3=_mm_load_ps(p+);
fSum0=_mm_add_ps(fSum0,fLoad0);//求和
fSum1=_mm_add_ps(fSum1,fLoad1);
fSum2=_mm_add_ps(fSum2,fLoad2);
fSum3=_mm_add_ps(fSum3,fLoad3);
p+=nBlockWidth;
}
fSum0=_mm_add_ps(fSum0,fSum1);
fSum2=_mm_add_ps(fSum2,fSum3);
fSum0=_mm_add_ps(fSum0,fSum2);
const float*q=(const float*)&fSum0;
s=q[]+q[]+q[]+q[]; //合并
for (unsigned int i=;i<cntRem;i++)//处理尾部剩余数据
{
s+=p[i];
}
return s;
}

完整代码

Timing.h

#include <windows.h>
static _LARGE_INTEGER time_start, time_over;
static double dqFreq;
static inline void startTiming()
{
_LARGE_INTEGER f;
QueryPerformanceFrequency(&f);
dqFreq=(double)f.QuadPart; QueryPerformanceCounter(&time_start);
}
static inline double stopTiming()
{
QueryPerformanceCounter(&time_over);
return ((double)(time_over.QuadPart-time_start.QuadPart)/dqFreq*);
}
#include <stdio.h>
#include <intrin.h>
#include <stdlib.h>
#include <time.h>
#include "Timing.h"
#define BUFSIZE 4096 // = 32KB{L1 Cache} / (2 * sizeof(float))
__declspec(align())float buf[BUFSIZE];//内存对齐
typedef float (*TESTPROC)(const float* pbuf, unsigned int cntbuf);//函数指针(用于测试时统一表示)
void RunTest(const char *szname,TESTPROC proc);
float sumfloat_base(const float *pbuf,unsigned int cntbuf);
float sumfloat_base_4loop(const float*pbuf,unsigned int cntbuf);
float sumfloat_sse(const float *pbuf ,unsigned int cntbuf);
float sumfloat_sse_4loop(const float *pbuf,unsigned int cntbuf); int main()
{
srand( (unsigned)time( NULL ) );
for (int i = ; i < BUFSIZE; i++)
buf[i] = (float)(rand() & 0x3f);// 使用&0x3f是为了让求和后的数值不会超过float类型的有效位数,便于观察结果是否正确.
RunTest("sumfloat_base",sumfloat_base);
RunTest("sumfloat_base_4loop",sumfloat_base_4loop);
RunTest("sumfloat_sse",sumfloat_sse);
RunTest("sumfloat_sse_4loop",sumfloat_sse_4loop);
return ;
} //测试函数
void RunTest(const char *szname,TESTPROC proc)
{
unsigned int testloop=;//循环次数 volatile float result;//volatile类型放止编译器优化使得循环内部不执行!
double mpsgood=;
double mps;
for (int k=;k<=;k++)//循环多次,选取最好情况
{
startTiming();
for(unsigned int i=;i<testloop;i++)
{
result=proc(buf,BUFSIZE);
}
double interval=stopTiming();
mps=testloop*BUFSIZE**1000.0/(interval**);//单位MB/s
if (mpsgood<mps)mpsgood=mps;
} printf("%s:\t%f,\t%.0lfMB/s\n",szname,result,mpsgood);//测速单位MB/s
}
//普通版
float sumfloat_base(const float *pbuf,unsigned int cntbuf)
{
float s=;
for (unsigned int i=;i<cntbuf;i++)
{
s+=pbuf[i];
}
return s;
} //四路展开
float sumfloat_base_4loop(const float*pbuf,unsigned int cntbuf)
{
float s=;
float fSum0=,fSum1=,fSum2=,fSum3=;
unsigned int i=;
const float *p=pbuf;
for (;i<=cntbuf-;i+=)//cntbuf-4不会每次都计算,编译器实现会计算好循环次数!
{
fSum0+=p[i];
fSum1+=p[i+];
fSum2+=p[i+];
fSum3+=p[i+];
}
s=fSum0+fSum1+fSum2+fSum3;
for (;i<cntbuf;i++)
{
s+=p[i];
}
return s;
}
//SSE版
float sumfloat_sse(const float *pbuf ,unsigned int cntbuf)
{
float s=;
int nBlockWidth=;//SSE一次处理4个float
int cntBlock=cntbuf/nBlockWidth;
int cntRem=cntbuf%nBlockWidth;
__m128 fSum=_mm_setzero_ps();//求和变量,初值清零
__m128 fLoad;
const float*p=pbuf;
for (unsigned int i=;i<cntBlock;i++)
{
fLoad=_mm_load_ps(p);//加载
fSum=_mm_add_ps(fSum,fLoad);//求和
p+=nBlockWidth;
}
const float *q=(const float*)&fSum;
s=q[]+q[]+q[]+q[]; //合并
for (int i=;i<cntRem;i++)//处理尾部剩余数据
{
s+=p[i];
}
return s;
} //SSE+四路展开
float sumfloat_sse_4loop(const float *pbuf,unsigned int cntbuf)
{
float s=;
unsigned int nBlockWidth=*;
unsigned int cntBlock=cntbuf/nBlockWidth;
unsigned int cntRem=cntbuf%nBlockWidth;
__m128 fSum0=_mm_setzero_ps();//求和变量,初值清零
__m128 fSum1=_mm_setzero_ps();
__m128 fSum2=_mm_setzero_ps();
__m128 fSum3=_mm_setzero_ps();
__m128 fLoad0,fLoad1,fLoad2,fLoad3;
const float *p=pbuf;
for (unsigned int i=;i<cntBlock;i++)
{
fLoad0=_mm_load_ps(p);//加载
fLoad1=_mm_load_ps(p+);
fLoad2=_mm_load_ps(p+);
fLoad3=_mm_load_ps(p+);
fSum0=_mm_add_ps(fSum0,fLoad0);//求和
fSum1=_mm_add_ps(fSum1,fLoad1);
fSum2=_mm_add_ps(fSum2,fLoad2);
fSum3=_mm_add_ps(fSum3,fLoad3);
p+=nBlockWidth;
}
fSum0=_mm_add_ps(fSum0,fSum1);
fSum2=_mm_add_ps(fSum2,fSum3);
fSum0=_mm_add_ps(fSum0,fSum2);
const float*q=(const float*)&fSum0;
s=q[]+q[]+q[]+q[]; //合并
for (unsigned int i=;i<cntRem;i++)//处理尾部剩余数据
{
s+=p[i];
}
return s;
}

测试结果:

[1]http://www.cnblogs.com/zyl910/archive/2012/10/22/simdsumfloat.html#undefined

SSE练习:单精度浮点数组求和的更多相关文章

  1. C# 使用SIMD向量类型加速浮点数组求和运算(1):使用Vector4、Vector<T>

    作者: 目录 一.缘由 二.使用向量类型 2.1 基本算法 2.2 使用大小固定的向量(如 Vector4) 2.2.1 介绍 2.2.2 用Vector4编写浮点数组求和函数 2.3 使用大小与硬件 ...

  2. js数组求和

    array1.reduce(callbackfn[, initialValue]) callback : 函数执行在数组中每个值 initialValue : 对象作为第一个参数回调的第一次调用使用 ...

  3. 《Intel汇编第5版》 数组求和

    一.LOOP指令 二.间接寻址 三.汇编数组求和 INCLUDE Irvine32.inc includelib Irvine32.lib includelib kernel32.lib includ ...

  4. Javascript数组求和的方法总结 以及由斐波那契数列得到的启发

    一次面试中,面试官要求用三种不同的Javascript方法进行一个数字数组的求和,当时思来想去只想到了使用循环这一种笨方法,因此面试比较失败,在这里总结了六种Javascript进行数组求和的方法,以 ...

  5. [java大数据面试] 2018年4月百度面试经过+三面算法题:给定一个数组,求和为定值的所有组合.

    给定一个数组,求和为定值的所有组合, 这道算法题在leetcode应该算是中等偏下难度, 对三到五年工作经验主要做业务开发的同学来说, 一般较难的也就是这种程度了. 简述经过: 不算hr面,总计四面, ...

  6. 个人项目-数组求和(语言:C++)

    prog1详细要求: [第一版本程序Prog1要求:] + 给定一个数组,实现数组元素求和:,具体要求:实现对一维数组(a[100])的所有元素相加运算. + 数据准备:a)数组长度:100:b)数组 ...

  7. 【原】C++11并行计算 — 数组求和

    本文转载请注明出处 -- polobymulberry-博客园 0x00 - 前言 最近想优化ORB-SLAM2,准备使用并行计算来提高其中ORB特征提取的速度.之前对并行计算方面一窍不通.借此机会, ...

  8. js 数组求和,多种方法,并比较性能

    可以借用下面12种方法对数组求和,创建一个长度为10w的数组,进行测试 every()       检测数值元素的每个元素是否都符合条件. filter()      检测数值元素,并返回符合条件所有 ...

  9. reduce实现数组求和

    对于实现数组求和,我们常用的思路是通过for.while,对数组进行迭代,依次将他们的值加起来,下面列举常用的两种方法 第一种: var arr = [1,2,3,4,5,6]; Array.prot ...

随机推荐

  1. Android JNI技术介绍【转】

    本文转载自:http://blog.csdn.net/yangwen123/article/details/8085833 JNI是JavaNative Interface 的缩写,通过JNI,Jav ...

  2. Jquery form表单提交

    起因 由于项目中原先提交from是通过JavaScript指定action,在submit提交的,使用的方式,也不是很标准,造成除了ie之外的浏览器都不能正常的提交数据,做web项目还是要考虑到浏览器 ...

  3. Java丨时间判断谁前谁后

    直奔主题: String date_str1 = "2016-06-02 23:03:123"; String date_str2 = "2016-06-03 03:03 ...

  4. UVA-10534 (LIS)

    题意: 给定一个长为n的序列,求一个最长子序列,使得该序列的长度为2*k+1,前k+1个数严格递增,后k+1个数严格单调递减; 思路: 可以先求该序列最长单调递增和方向单调递增的最长序列,然后枚举那第 ...

  5. java实现EXCEL数据导入到数据库中的格式问题的解决

    之前作为项目甲方,加之java接触不多,在java web开发方面都是打下手的份. 对于EXCEL数据导入到数据库这个问题一直老是出现格式原因而导入失败也是未免惆怅,开发团队也是只说回去检查一下格式. ...

  6. 使用cygwin注意事项二

    使用cygwin时,一定要区分当前运行的是cygwin下的进程还是windows下的进程,如:使用vim, 假如cygwin下没安装vim, windows下安装了,那么你运行的就是windows下的 ...

  7. bzoj 4589 Hard Nim —— FWT

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4589 先手必败,是一开始所有石子的异或和为0: 生成函数 (xpri[1] + xpri[2 ...

  8. Sense2vec with spaCy and Gensim

    如果你在2015年做过文本分析项目,那么你大概率用的是word2vec模型.Sense2vec是基于word2vec的一个新模型,你可以利用它来获取更详细的.与上下文相关的词向量.本文主要介绍该模型的 ...

  9. UVa 820 Internet Bandwidth (裸板网络流)

    题意:有一个计算机网络,输入节点数n,输入网络流源点和汇点src,des,再输入双向边数m.给出m条边的负载,求最大流. 析:直接上网络流的最大流. 代码如下: #pragma comment(lin ...

  10. 2019ICPC西安邀请赛 - B. Product - 数论

    打印的时候麻烦把:https://blog.csdn.net/skywalkert/article/details/50500009这个打印下来. 求\(\prod\limits_{i=1}^{n} ...