您好,欢迎来到抵帆知识网。
搜索
您的当前位置:首页基于SSE指令集的程序设计简介

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

来源:抵帆知识网
基于SSE指令集的程序设计简介

疯狂代码 http://www.crazycoder.cn/ ĵ:http:/www.crazycoder.cn/DataBase/Article1.html

SSE技术介绍

Intel公司单指令多数据流式扩展(SSEStreaming SIMD Extensions)技术能够有效增强CPU浮点运算能力VisualStudio .NET 2003提供了对SSE指令集编程支持从而允许用户在C代码中不用编写汇编代码就可直接使用SSE指令功能MSDN中有关SSE技术主题[1]有可能会使不熟悉使用SSE汇编指令编程初学者感到困惑但是在阅读

MSDN有关文档同时参考下Intel软件Software介绍说明书(Intel Software manuals)[2]会使你更清楚地理解使用SSE指令编程要点

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

for each f in .gif' /> //对中每个元素 f = sqrt(f) //计算它平方根

为了了解实现细节我们把上面代码这样写:

for each f in .gif' /> {

把f从内存加载到浮点寄存器 计算平方根

再把计算结果从寄存器中取出放入内存 }

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

for each 4 members in .gif' /> //对中每4个元素 {

把中这4个数加载到个128位SSE寄存器中

在个CPU指令执行周期中完成计算这4个数平方根操作

把所得4个结果取出写入内存 }

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

SSE设计详细介绍

包含头文件:

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

中用到SSE处理器指令是由编译器决定所以它并没有相关.lib库文件

数据分组(Data Alignment)

由SSE指令处理每个浮点数必须把其中需要处理数每16个字节(128位 2进制)分为组个静态( .gif' />)可由__declspec(align(16))关键字声明:

__declspec(align(16)) float m_fArray[ARRAY_SIZE];

动态(dynamic .gif' />)可由_aligned_malloc为其分配空间:

m_fArray = (float*) _aligned_malloc(ARRAY_SIZE * (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/ssero/SSE_src.zip下载举例压缩包该压缩包中含有两个项目这两个项目是基于微软基本类库(MFC)建立Visual C.NET项目你也可以按照下面讲解建立这两个项目

SSETest 举例项目

SSETest项目是个基于对话框应用它用到了 3个浮点参和运算:

fResult[i] = sqrt( fSource1[i]*fSource1[i] + fSource2[i]*fSource2[i] ) + 0.5

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

其中ARRAY_SIZE被定义为30000数据源(Source)通过使用sin和cos给它赋值我们用Kris Jearakul开发瀑布状图表Control控件(Waterfall chart control)[3] 来显示参和计算源和结果计算所需时间(以毫秒ms为单位)在对话框中显示出来我们使用 3种区别途径来完成计算:

纯C代码;

使用SSE指令C代码; 包含SSE汇编指令代码

纯C代码:

void CSSETestDlg::ComputeArrayCPlusPlus( float* pArray1, // [输入] 源1 float* pArray2, // [输入] 源2

float* pResult, // [输出] 用来存放结果 nSize) // [输入] 大小 { 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软件Software介绍说明书(Intel Software manuals)中有关SSE汇编指令介绍说明首先我是在第卷第 9章找到相关SSE指令然后在第 2卷找到了这些SSE指令详细介绍说明这些介绍说明有部分涉及了和其特性相关C然后我通过这些SSE指令对应C查找了MSDN中和其相关介绍说明搜索结果见下表:

实现功能 对应SSE汇编指令 Visual C.NET中SSE

将4个32位浮点数放进个128位存储单元 movss 和 shufps _mm__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, // [输出] 用来存放结果 nSize) // [输入] 大小 {

nLoop = nSize/ 4;

__m128 m1, m2, m3, m4;

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

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

for ( 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, // [输出] 用来存放结果 nSize) // [输入] 大小 {

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 毫秒 使用SSEC 计算所用时间是 9 毫秒

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

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

SSESample 举例项目

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

fResult[i] = sqrt(fSource[i]*2.8)

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

这个同时计算了中最大值和最小值ARRAY_SIZE被定义为100000中计算结果在列表框中显示出来其中在我机子上用下面 3种思路方法计算所需时间是: 纯C代码计算 6 毫秒 使用SSEC 计算 3 毫秒

使用SSE汇编指令计算 2 毫秒

大家看到使用SSE汇编指令计算结果会好些使用了效率增强了SSX寄存器组但是在通常情况下使用SSEC 计算会比汇编代码计算效率更高些C编译器优化后代码有很高运算效率若要使汇编代码比优化后代码运算效率更高这通常是很难做到

纯C代码:

// 输入: m_fInitialArray

// 输出: m_fResultArray, m_fMin, m_fMax

void CSSESampleDlg::OnBnClickedButtonCplusplus {

m_fMin = FLT_MAX; m_fMax = FLT_MIN; i;

for ( i = 0; i < ARRAY_SIZE; i ) {

m_fResultArray[i] = sqrt(m_fInitialArray[i] * 2.8f);

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

( 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__ps1(2.8f); // coeff[0, 1, 2, 3] = 2.8 __m128 tmp;

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

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

for ( 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]))); }

2009-2-12 5:25:27

疯狂代码 http://www.crazycoder.cn/

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- dfix.cn 版权所有 湘ICP备2024080961号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务