SSE练习:单精度浮点数组求和

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=0;
	for (unsigned int i=0;i<cntbuf;i++)
	{
		s+=pbuf[i];
	}
	return s;
}


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

//四路展开
float sumfloat_base_4loop(const float*pbuf,unsigned int cntbuf)
{
	float s=0;
	float fSum0=0,fSum1=0,fSum2=0,fSum3=0;
	unsigned int i=0;
	const float *p=pbuf;
	for (;i<=cntbuf-4;i+=4)//cntbuf-4不会每次都计算,编译器实现会计算好循环次数!
	{
		fSum0+=p[i];
		fSum1+=p[i+1];
		fSum2+=p[i+2];
		fSum3+=p[i+3];
	}
	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=0;
	int nBlockWidth=4;//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=0;i<cntBlock;i++)
	{
		fLoad=_mm_load_ps(p);//加载
		fSum=_mm_add_ps(fSum,fLoad);//求和
		p+=nBlockWidth;
	}
	const float *q=(const float*)&fSum;
	s=q[0]+q[1]+q[2]+q[3];		//合并
	for (int i=0;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=0;
	unsigned int nBlockWidth=4*4;
	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=0;i<cntBlock;i++)
	{
		fLoad0=_mm_load_ps(p);//加载
		fLoad1=_mm_load_ps(p+4);
		fLoad2=_mm_load_ps(p+8);
		fLoad3=_mm_load_ps(p+12);
		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[0]+q[1]+q[2]+q[3];				//合并
	for (unsigned int i=0;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*1000);
}

#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(16))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 = 0; 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 0;
}


//测试函数
void RunTest(const char *szname,TESTPROC proc)
{
	unsigned int testloop=4000;//循环次数

	volatile float result;//volatile类型放止编译器优化使得循环内部不执行!
	double mpsgood=0;
	double mps;
	for (int k=0;k<=3;k++)//循环多次,选取最好情况
	{
		startTiming();
		for(unsigned int i=0;i<testloop;i++)
		{
			result=proc(buf,BUFSIZE);
		}
		double interval=stopTiming();
		mps=testloop*BUFSIZE*4*1000.0/(interval*1024*1024);//单位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=0;
	for (unsigned int i=0;i<cntbuf;i++)
	{
		s+=pbuf[i];
	}
	return s;
}

//四路展开
float sumfloat_base_4loop(const float*pbuf,unsigned int cntbuf)
{
	float s=0;
	float fSum0=0,fSum1=0,fSum2=0,fSum3=0;
	unsigned int i=0;
	const float *p=pbuf;
	for (;i<=cntbuf-4;i+=4)//cntbuf-4不会每次都计算,编译器实现会计算好循环次数!
	{
		fSum0+=p[i];
		fSum1+=p[i+1];
		fSum2+=p[i+2];
		fSum3+=p[i+3];
	}
	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=0;
	int nBlockWidth=4;//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=0;i<cntBlock;i++)
	{
		fLoad=_mm_load_ps(p);//加载
		fSum=_mm_add_ps(fSum,fLoad);//求和
		p+=nBlockWidth;
	}
	const float *q=(const float*)&fSum;
	s=q[0]+q[1]+q[2]+q[3];		//合并
	for (int i=0;i<cntRem;i++)//处理尾部剩余数据
	{
		s+=p[i];
	}
	return s;
}

//SSE+四路展开
float sumfloat_sse_4loop(const float *pbuf,unsigned int cntbuf)
{
	float s=0;
	unsigned int nBlockWidth=4*4;
	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=0;i<cntBlock;i++)
	{
		fLoad0=_mm_load_ps(p);//加载
		fLoad1=_mm_load_ps(p+4);
		fLoad2=_mm_load_ps(p+8);
		fLoad3=_mm_load_ps(p+12);
		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[0]+q[1]+q[2]+q[3];				//合并
	for (unsigned int i=0;i<cntRem;i++)//处理尾部剩余数据
	{
		s+=p[i];
	}
	return s;
}


测试结果:


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



  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

聚沙塔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值