【转】【SEE】基于SSE指令集的程序设计简介

SSE技术简介

Intel公司的单指令多数据流式扩展(SSE,Streaming SIMD Extensions)技术能够有效增强CPU浮点运算的能力。Visual

Studio .NET

2003提供了对SSE指令集的编程支持,从而允许用户在C++代码中不用编写汇编代码就可直接使用SSE指令的功能。MSDN中有关SSE技术的主题

[1]有可能会使不熟悉使用SSE汇编指令编程的初学者感到困惑,但是在阅读MSDN有关文档的同时,参考一下Intel软件说明书(Intel
Software
manuals)[2]会使你更清楚地理解使用SSE指令编程的要点。

SIMD(single-instruction,

multiple-data)是一种使用单道指令处理多道数据流的CPU执行模式,即在一个CPU指令执行周期内用一道指令完成处理多个数据的操作。考虑一下下面这个任务:计算一个很长的浮点型数组中每一个元素的平方根。实现这个任务的算法可以这样写:

for

each  f in array    
   //对数组中的每一个元素
    f =
sqrt(f)           //计算它的平方根
为了了解实现的细节,我们把上面的代码这样写:

for
each  f in array
{
   
   
把f从内存加载到浮点寄存器
    计算平方根
   
再把计算结果从寄存器中取出放入内存
}

具有Intel

SSE指令集支持的处理器有8个128位的寄存器,每一个寄存器可以存放4个(32位)单精度的浮点数。SSE同时提供了一个指令集,其中的指令可以允许把浮点数加载到这些128位的寄存器之中,这些数就可以在这些寄存器中进行算术逻辑运算,然后把结果放回内存。采用SSE技术后,算法可以写成下面的样子:

for

each  4 members in array  //对数组中的每4个元素
{
 
 
    把数组中的这4个数加载到一个128位的SSE寄存器中
   
在一个CPU指令执行周期中完成计算这4个数的平方根的操作
   
把所得的4个结果取出写入内存
}

C++

编程人员在使用SSE指令函数编程时不必关心这些128位的寄存器,你可以使用128位的数据类型“__m128”和一系列C++函数来实现这些算术和逻辑操作,而决定程序使用哪个SSE寄存器以及代码优化是C++编译器的任务。当需要对很长的浮点数数组中的元素进行处理的时候,SSE技术确实是一种很高效的方法。

SSE程序设计详细介绍

包含的头文件:

所有的SSE指令函数和__m128数据类型都在xmmintrin.h文件中定义:
#include

<xmmintrin.h>
因为程序中用到的SSE处理器指令是由编译器决定,所以它并没有相关的.lib库文件。

数据分组(Data

Alignment)

由SSE指令处理的每一个浮点数数组必须把其中需要处理的数每16个字节(128位二进制)分为一组。一个静态数组(static

array)可由__declspec(align(16))关键字声明:

__declspec(align(16)) float

m_fArray[ARRAY_SIZE];

动态数组(dynamic

array)可由_aligned_malloc函数为其分配空间:
m_fArray = (float*)

_aligned_malloc(ARRAY_SIZE * sizeof(float),

16);

由_aligned_malloc函数分配空间的动态数组可以由_aligned_free函数释放其占用的空间:
_aligned_free(m_fArray);

__m128

数据类型

该数据类型的变量可用做SSE指令的操作数,它们不能被用户指令直接存取。_m128类型的变量被自动分配为16个字节的字长。

CPU对SSE指令集的支持

如果你的CPU能够具有了SSE指令集,你就可以使用Visual

Studio .NET 2003提供的对SSE指令集支持的C++函数库了,你可以查看MSDN中的一个Visual C++

CPUID的例子[4],它可以帮你检测你的CPU是否支持SSE、MMX指令集或其它的CPU功能。

编程实例

以下讲解了SSE技术在Visual Studio .NET 2003下的应用实例,你可以在http://www.codeproject.com/cpp/sseintro/SSE_src.zip下载示例程序压缩包。该压缩包中含有两个项目,这两个项目是基于微软基本类库(MFC)建立的Visual

C++.NET项目,你也可以按照下面的讲解建立这两个项目。

SSETest

示例项目

SSETest项目是一个基于对话框的应用程序,它用到了三个浮点数组参与运算:

fResult = sqrt(

fSource1*fSource1 + fSource2*fSource2 ) + 0.5

其中i = 0, 1, 2
...

ARRAY_SIZE-1

其中ARRAY_SIZE被定义为30000。数据源数组(Source数组)通过使用sin和cos函数给它赋值,我们用Kris

Jearakul开发的瀑布状图表控件(Waterfall chart control)[3]

来显示参与计算的源数组和结果数组。计算所需的时间(以毫秒ms为单位)在对话框中显示出来。我们使用三种不同的途径来完成计算:

纯C++代码;
使用SSE指令函数的C++代码;
包含SSE汇编指令的代码。

纯C++代码:


void CSSETestDlg::ComputeArrayCPlusPlus(
float* pArray1, // [输入] 源数组1
float* pArray2, // [输入] 源数组2
float* pResult, // [输出] 用来存放结果的数组
int nSize) // [输入] 数组的大小
{

int i;

float* pSource1 = pArray1;
float* pSource2 = pArray2;
float* pDest = pResult;

for ( i = 0; i < nSize; i++ )
{
*pDest = (float)sqrt((*pSource1) * (*pSource1) + (*pSource2)
* (*pSource2)) + 0.5f;

pSource1++;
pSource2++;
pDest++;
}
}

下面我们用具有SSE特性的C++代码重写上面这个函数。为了查询使用SSE指令C++函数的方法,我参考了Intel软件说明书(Intel Software
manuals)中有关SSE汇编指令的说明,首先我是在第一卷的第九章找到的相关SSE指令,然后在第二卷找到了这些SSE指令的详细说明,这些说明有一部分涉及了与其特性相关的C++函数。然后我通过这些SSE指令对应的C++函数查找了MSDN中与其相关的说明。搜索的结果见下表:






















实现的功能 对应的SSE汇编指令 Visual
C++.NET中的SSE函数
将4个32位浮点数放进一个128位的存储单元。 movss 和 shufps _mm_set_ps1
将4对32位浮点数同时进行相乘操作。这4对32位浮点数来自两个128位的存储单元,再把计算结果(乘积)赋给一个128位的存储单元。 mulps _mm_mul_ps
将4对32位浮点数同时进行相加操作。这4对32位浮点数来自两个128位的存储单元,再把计算结果(相加之和)赋给一个128位的存储单元。 addps _mm_add_ps
对一个128位存储单元中的4个32位浮点数同时进行求平方根操作。 sqrtps _mm_sqrt_ps

使用Visual C++.NET的 SSE指令函数的代码:


void CSSETestDlg::ComputeArrayCPlusPlusSSE(
float* pArray1, // [输入] 源数组1
float* pArray2, // [输入] 源数组2
float* pResult, // [输出] 用来存放结果的数组
int nSize) // [输入] 数组的大小
{
int nLoop = nSize/ 4;

__m128 m1, m2, m3, m4;

__m128* pSrc1 = (__m128*) pArray1;
__m128* pSrc2 = (__m128*) pArray2;
__m128* pDest = (__m128*) pResult;

__m128 m0_5 = _mm_set_ps1(0.5f); // m0_5[0, 1, 2, 3] = 0.5

for ( int i = 0; i < nLoop; i++ )
{
m1 = _mm_mul_ps(*pSrc1, *pSrc1); // m1 = *pSrc1 * *pSrc1
m2 = _mm_mul_ps(*pSrc2, *pSrc2); // m2 = *pSrc2 * *pSrc2
m3 = _mm_add_ps(m1, m2); // m3 = m1 + m2
m4 = _mm_sqrt_ps(m3); // m4 = sqrt(m3)
*pDest = _mm_add_ps(m4, m0_5); // *pDest = m4 + 0.5

pSrc1++;
pSrc2++;
pDest++;
}
}

使用SSE汇编指令实现的C++函数代码:


void CSSETestDlg::ComputeArrayAssemblySSE(
float* pArray1, // [输入] 源数组1
float* pArray2, // [输入] 源数组2
float* pResult, // [输出] 用来存放结果的数组
int nSize) // [输入] 数组的大小
{
int nLoop = nSize/4;
float f = 0.5f;

_asm
{
movss xmm2, f // xmm2[0] = 0.5
shufps xmm2, xmm2, 0 // xmm2[1, 2, 3] = xmm2[0]

mov esi, pArray1 // 输入的源数组1的地址送往esi
mov edx, pArray2 // 输入的源数组2的地址送往edx

mov edi, pResult // 输出结果数组的地址保存在edi
mov ecx, nLoop //循环次数送往ecx

start_loop:
movaps xmm0, [esi] // xmm0 = [esi]
mulps xmm0, xmm0 // xmm0 = xmm0 * xmm0

movaps xmm1, [edx] // xmm1 = [edx]
mulps xmm1, xmm1 // xmm1 = xmm1 * xmm1

addps xmm0, xmm1 // xmm0 = xmm0 + xmm1
sqrtps xmm0, xmm0 // xmm0 = sqrt(xmm0)

addps xmm0, xmm2 // xmm0 = xmm1 + xmm2

movaps [edi], xmm0 // [edi] = xmm0

add esi, 16 // esi += 16
add edx, 16 // edx += 16
add edi, 16 // edi += 16

dec ecx // ecx--
jnz start_loop //如果不为0则转向start_loop
}
}

最后,在我的计算机上运行计算测试的结果:

纯C++代码计算所用的时间是26 毫秒
使用SSE的C++ 函数计算所用的时间是 9

毫秒
包含SSE汇编指令的C++代码计算所用的时间是 9

毫秒

以上的时间结果是在Release优化编译后执行程序得出的。

SSESample

示例项目


SSESample项目是一个基于对话框的应用程序,其中它用下面的浮点数数组进行计算:

fResult

= sqrt(fSource*2.8)

其中i = 0, 1, 2 ...

ARRAY_SIZE-1

这个程序同时计算了数组中的最大值和最小值。ARRAY_SIZE被定义为100000,数组中的计算结果在列表框中显示出来。其中在我的机子上用下面三种方法计算所需的时间是:

纯C++代码计算 

                 6
毫秒
使用SSE的C++ 函数计算     3 毫秒
使用SSE汇编指令计算 
       2

毫秒

大家看到,使用SSE汇编指令计算的结果会好一些,因为使用了效率增强了的SSX寄存器组。但是在通常情况下,使用SSE的C++

函数计算会比汇编代码计算的效率更高一些,因为C++编译器的优化后的代码有很高的运算效率,若要使汇编代码比优化后的代码运算效率更高,这通常是很难做到的。

纯C++代码:


// 输入: m_fInitialArray
// 输出: m_fResultArray, m_fMin, m_fMax
void CSSESampleDlg::OnBnClickedButtonCplusplus()
{
m_fMin = FLT_MAX;
m_fMax = FLT_MIN;

int i;

for ( i = 0; i < ARRAY_SIZE; i++ )
{
m_fResultArray[i] = sqrt(m_fInitialArray[i] * 2.8f);

if ( m_fResultArray[i] < m_fMin )
m_fMin = m_fResultArray[i];

if ( m_fResultArray[i] > m_fMax )
m_fMax = m_fResultArray[i];
}
}

使用Visual C++.NET的 SSE指令函数的代码:


// 输入: m_fInitialArray
// 输出: m_fResultArray, m_fMin, m_fMax
void CSSESampleDlg::OnBnClickedButtonSseC()
{
__m128 coeff = _mm_set_ps1(2.8f); // coeff[0, 1, 2, 3] = 2.8
__m128 tmp;

__m128 min128 = _mm_set_ps1(FLT_MAX); // min128[0, 1, 2, 3] = FLT_MAX
__m128 max128 = _mm_set_ps1(FLT_MIN); // max128[0, 1, 2, 3] = FLT_MIN

__m128* pSource = (__m128*) m_fInitialArray;
__m128* pDest = (__m128*) m_fResultArray;

for ( int i = 0; i < ARRAY_SIZE/4; i++ )
{
tmp = _mm_mul_ps(*pSource, coeff); // tmp = *pSource * coeff
*pDest = _mm_sqrt_ps(tmp); // *pDest = sqrt(tmp)

min128 = _mm_min_ps(*pDest, min128);
max128 = _mm_max_ps(*pDest, max128);

pSource++;
pDest++;
}

// 计算max128的最大值和min128的最小值
union u
{
__m128 m;
float f[4];
} x;

x.m = min128;
m_fMin = min(x.f[0], min(x.f[1], min(x.f[2], x.f[3])));

x.m = max128;
m_fMax = max(x.f[0], max(x.f[1], max(x.f[2], x.f[3])));
}

使用SSE汇编指令的C++函数代码:


// 输入: m_fInitialArray
// 输出: m_fResultArray, m_fMin, m_fMax
void CSSESampleDlg::OnBnClickedButtonSseAssembly()
{

float* pIn = m_fInitialArray;
float* pOut = m_fResultArray;

float f = 2.8f;
float flt_min = FLT_MIN;
float flt_max = FLT_MAX;

__m128 min128;
__m128 max128;

// 使用以下的附加寄存器:xmm2、xmm3、xmm4:
// xmm2 – 相乘系数
// xmm3 – 最小值
// xmm4 – 最大值

_asm
{
movss xmm2, f // xmm2[0] = 2.8
shufps xmm2, xmm2, 0 // xmm2[1, 2, 3] = xmm2[0]

movss xmm3, flt_max // xmm3 = FLT_MAX
shufps xmm3, xmm3, 0 // xmm3[1, 2, 3] = xmm3[0]

movss xmm4, flt_min // xmm4 = FLT_MIN
shufps xmm4, xmm4, 0 // xmm3[1, 2, 3] = xmm3[0]

mov esi, pIn // 输入数组的地址送往esi
mov edi, pOut // 输出数组的地址送往edi
mov ecx, ARRAY_SIZE/4 // 循环计数器初始化

start_loop:
movaps xmm1, [esi] // xmm1 = [esi]
mulps xmm1, xmm2 // xmm1 = xmm1 * xmm2
sqrtps xmm1, xmm1 // xmm1 = sqrt(xmm1)
movaps [edi], xmm1 // [edi] = xmm1

minps xmm3, xmm1
maxps xmm4, xmm1

add esi, 16
add edi, 16

dec ecx
jnz start_loop

movaps min128, xmm3
movaps max128, xmm4
}

union u
{
__m128 m;
float f[4];
} x;

x.m = min128;
m_fMin = min(x.f[0], min(x.f[1], min(x.f[2], x.f[3])));

x.m = max128;
m_fMax = max(x.f[0], max(x.f[1], max(x.f[2], x.f[3])));
}

测试程序下载:http://pan.baidu.com/s/1mgHRzNM

原文地址:http://club.topsage.com/thread-551227-1-1.html

时间: 2024-10-29 10:46:14

【转】【SEE】基于SSE指令集的程序设计简介的相关文章

【转】【SSE】基于SSE指令集的程序设计简介

基于SSE指令集的程序设计简介 作者:Alex Farber 出处:http://www.codeproject.com/cpp/sseintro.asp SSE技术简介 Intel公司的单指令多数据流式扩展(SSE,Streaming SIMD Extensions)技术能够有效增强CPU浮点运算的能力.Visual Studio .NET 2003提供了对SSE指令集的编程支持,从而允许用户在C++代码中不用编写汇编代码就可直接使用SSE指令的功能.MSDN中有关SSE技术的主题[1]有可能

【转】【MMX】 基于MMX指令集的程序设计简介

(一) MMX技术简介 Intel 公司的MMX™(多媒体增强指令集)技术可以大大提高应用程序对二维三维图形和图象的处理能力.Intel MMX技术可用于对大量数据和复杂数组进行的复杂处理,使用MMX技术可处理的数据基本单位可以是字节(byte).字(word),或者是双字(double-word).Visual Studio .NET 2003提供了对MMX指令集特性的支持,从而可以不必编写汇编代码,直接使用C++代码就可以实现MMX指令的功能.通过参考Intel软件说明书(Intel Sof

javascript高级程序设计--简介

工作一年多了,这一年的收获真是丰富.结识了许多同事朋友,技术网友,学了许多新的技术知识:当然还要感谢我的朋友们,感谢我的第一家公司. 大学主要学的.net,刚毕业那会对javascript的了解几乎就是空白,后来有机会接触了百度地图,开始了javascript学习之路.现在在项目中也能熟练的使用javascript,jquery等技术,可总感觉缺点什么,有些东西你知道是什么,也能写出来,但就是不知道他是什么,他的原理是什么样的.所以才有了现在的想法,系统的学习一遍javascript.---文笔

[推荐]ORACLE PL/SQL编程详解之一:PL/SQL 程序设计简介(千里之行,始于足下)

原文:[推荐]ORACLE PL/SQL编程详解之一:PL/SQL 程序设计简介(千里之行,始于足下) [推荐]ORACLE PL/SQL编程详解之一: PL/SQL 程序设计简介(千里之行,始于足下) ——通过知识共享树立个人品牌. 继上六篇: [顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功) [推荐]ORACLE PL/SQL编程详解之三:PL/SQL流程控制语句(不给规则,不成方圆) [推荐]ORACLE PL/SQL编程之四:把游标说透(

Lucene:基于Java的全文检索引擎简介

Lucene是一个基于Java的全文索引工具包. 基于Java的全文索引引擎Lucene简介:关于作者和Lucene的历史 全文检索的实现:Luene全文索引和数据库索引的比较 中文切分词机制简介:基于词库和自动切分词算法的比较 具体的安装和使用简介:系统结构介绍和演示 Hacking Lucene:简化的查询分析器,删除的实现,定制的排序,应用接口的扩展 从Lucene我们还可以学到什么 另外,如果是在选择全文引擎,现在也许是试试 Sphinx的时候了:相比Lucene速度更快, 有中文分词的

基于EEPlat的项目开发过程简介

EEPlat平台比较匹配敏捷式开发过程(如XP),可以进行简单设计快速迭代,基本可以以一周或两周作为迭代周期.当然了EEPlat也可以支持传统重量级的软件开发过程(如RUP等). 项目开始初期,与传统项目一样进行需求调研,进行需求分析,经过和客户讨论确定后可以获得一个相对完整的软件需求.然后进行简单设计,进行功能分析.流程分析.数据分析.基于EEPlat开发首先需要完成数据库设计,数据库设计完成后,即可进行开发.功能分析和流程分析相对比较简单即可,后期平台在这方面的调整会很快,完全可以通过与客户

PL/SQL 程序设计简介

 ①PL/SQL 程序设计简介 PL/SQL是一种高级数据库程序设计语言,该语言专门用于在各种环境下对ORACLE数据库进行访问.由于该语言集成于数据库服务器中, 所以PL/SQL代码可以对数据进行快速高效的处理 在PL/SQL中可以使用的SQL语句有: INSERT,UPDATE,DELETE,SELECT -INTO,COMMIT,ROLLBACK,SAVEPOINT. 提示:在PL/SQL中只能用SQL语句中的DML 部分,不能用DDL 部分,如果要在PL/SQL中使用DDL(如CRE

SSE指令集学习:Compiler Intrinsic

大多数的函数是在库中,Intrinsic Function却内嵌在编译器中(built in to the compiler). 1. Intrinsic Function Intrinsic Function作为内联函数,直接在调用的地方插入代码,即避免了函数调用的额外开销,又能够使用比较高效的机器指令对该函数进行优化.优化器(Optimizer)内置的一些Intrinsic Function行为信息,可以对Intrinsic进行一些不适用于内联汇编的优化,所以通常来说Intrinsic Fu

MT3332基于主机的解决方案技术简介免费下载

今天分享的MT3332的基础资料和基于主机的解决方案技术简介,资料太多,不方便上传,有兴趣的朋友可以到闯客网技术论坛下载,或者加群获取更多联发科芯片资料:813238832 一般描述MT3332是一种高性能的单芯片多GNSS解决方案,包括片上CMOS RF和数字基带.它能够达到业界最高水平的灵敏度.准确度和第一次修复的时间(TTFF)的最小功耗的小脚印无铅封装.其占地面积小,BOM要求最低,为设计提供了显著的减少.便携式应用所需的制造和测试资源. MT332即使在室内信号电平下也能在最短的时间内