分类: C/C++
2008-08-04 09:28:53
概要 :
随着Intel Pentium III处理器的发布,给程序设计人员又带来了许多新的特性。利用这些新特性,程序员可以为用户创造出更好的产品.
Pentium III和Pentium III Xeon(至强处理器)的许多新特性,可以使她能够比Pentium II和Pentium II
Xeon处理器有更快的运行速度,这些新特性包括一个处理器序列号(unique processor ID)和新增SSE处理器指令集,这些新的指令集就像Pentium
II在经典Pentium的基础上添加的MMX指令集.
1. 使用SSE
在具体描述了SSE指令集以后,让我们看看怎样才能在应用程序中使用他们呢.
1.1 汇编语言
传统地,程序员希望能够使用汇编语言来利用新处理器的新特性.通常这是必须的,因为高级的程序开发工具只有在处理器正式推出以后的某个适当的时间才会由新版本发布支持.
Pentium III的情况也是这样.现在,只有Intel的C/C 编译器和Microsoft Macro Assembler(6.11d及以上版本)才认识新的SSE指令集.
这里有一个矛盾的地方:如果用纯汇编语言来写一个大型的、复杂的应用程序是非常困难的,但是这样写出来的代码执行速度又是最快的.
我们也可以使用SSE SDK(Software
Developers Kit,软件开发工具包)开发包,Intel在开发包中提供了两种编程机制去使用SSE指令集:一个intrinsics库和一个表示SSE定义的新数据类型的C 类.使用这些机制比单纯用汇编语言简单.这是很明显的,因为这样程序员再也不用由自己去管理SSE的寄存器了,可以很方便的创建出大型的应用程序.但是这种机制写出的代码又比用汇编语言写的代码执行速度慢了.图6说明了这三种开发方法在程序执行速度和开发困难程度之间的矛盾.
图六: 在不同开发环境下的程序执行速度和开发困难程度之间的矛盾
1.1.1 示例:乘法
假设有两个128位的数a和b分别存储在寄存器xmm1和xmm2中,他们的计算结果保存在寄存器xmm0中.用C语言嵌入汇编的代码如下:
#include
...
_asm {
push esi;
push edi;
; a is loaded into xmm1
; b is loaded into xmm2
mov xmm0, xmm1;
mulps xmm0, xmm2;
; store result into c
pop edi;
pop esi;
}
...
图7用图表来表示了这种包裹乘法(packed multiplication)的计算
图七:包裹乘法计算
1.2 Intrinsics库
使用Intrinsics库是第一种附加的编程机制.Intrinsics库为C语言提供了一个使用SSE指令集的接口.所有的SSE指令在这个库中都被包装成了C函数.两个包裹数据(packed
data)相加的汇编指令是addps.相应的,在intrinsics库中将两个包裹数据相加的对应函数是_mm_add_ps.为了配合这些新增的函数,intrinsics库还定义了一个新的数据类型(__m128)来表示128位长的数据,可以用来保存4个单精度浮点数.
要使用intrinsics库,还需要在程序中包含(include)xmmintrin.h头文件.
1.2.1 示例:乘法
假设有两个128位数a和b,他们的计算结果将被保存在另一个128位数c中.a,b和c都是__m128数据.__m128是128位数据类型,已经在头文件xmmintrin.h中定义了.函数_mm_set_ps功能是把他的4个参数按照第一个参数为最高位、最后一个参数为最低位的顺序排列组合成一个128数.
#include
...
__m128 a, b, c;
a = _mm_set_ps(4, 3, 2, 1)
b = _mm_set_ps(4, 3, 2, 1)
c = _mm_set_ps(0, 0, 0, 0)
c = _mm_mul_ps(a, b);
...
1.3 C
第二种附加的编程机制是使用C 语言.SSE SDK开发包提供了一个C 类:F32vec4,用来处理和表示一个128位的新数据类型.所有的新数据类型的操作都被封装在这个类中了.在类的内部,他也是使用intrinsics库的.
要使用这个C 类,,还必须在程序中包含(include)fvec.h头文件.
1.3.1 示例:乘法
我们再次假设有两个128位的数a和b,他们的计算结果放在另一个128位数c中.所有的数据都定义成F32vec4类.类的构造函数功能就相当于_mm_set_ps函数.
#include
...
F32vec4 a(4, 3, 2, 1), b(4, 3, 2, 1), c(0, 0, 0, 0);
...
c =a * b;
...
1.4 编译器支持
前面已经说过,只有Intel的C\C 编译器和Microsoft的Macro Assembler支持新的SSE指令集.Intel编译器已经整合到Microsoft的Visual
Studio集成开发环境中了.Visual Studio集成开发环境可以被配置成使用Intel的编译器来编译整个工程或者工程中的某个文件.
2. SSE指令详细资料
在我们介绍SSE指令用法的例子以前,让我们先来看看SSE指令集的所有指令列表.
Arithmetic Instructions(算术指令)
addps, addss
subps, subss
mulps, mulss
divps, divss
sqrtps, sqrtss
maxps, maxss
minps, minss
Logical Instructions(逻辑指令)
andps
andnps
orps
xorps
Compare Instructions(比较指令)
cmpps, cmpss
comiss
ucomiss
Shuffle Instructions(清洗指令)
shufps
unpchkps
unpcklps
Conversion Instructions(转换指令)
cvtpi2ps, cvtpi2ss
cvtps2pi, cvtss2si
Data Movement Instructions(数据移动指令)
movaps
movups
movhps
movlps
movmskps
movss
State Management Instructions(状态管理指令)
ldmxcsr
fxsave
stmxscr
fxstor
Cacheability Control Instructions(cache控制指令)
maskmovq
movntq
movntps
prefetch
sfence
Additional SIMD Integer Instructions(附加的SIMD整数指令)
pextrw
pinsrw
pmaxub, pmaxsw
pminub, pminsw
pmovmskb
pmulhuw
pshufw
3. 例子
在这一节,我们将介绍几个例子来帮你理解Pentium III的SSE指令集应用.在每个例子中,我们都将介绍三种解决方案,分别使用汇编语言、intrinsics库和C 类.附加例子将在下一部分的附加示例节中介绍.
3.1 包裹乘法
在前面我们已经用三种不同的开发机制介绍了两个包裹数据的乘法计算,这三种机制分别是使用汇编语言、intrinsics库和C 类来编写.
3.2 比较操作
让我们来考虑一下比较的事件.如果不使用SSE,我们每次只能对一对浮点数进行比较.使用了SSE以后,可以同时对4对浮点数进行比较.
在C和C 中比较4对浮点数,我们可以像下面一样写一个循环,把每次的比较放在循环里面.另外我们还需要定义保存比较结果的变量.代码可以类似于下面的例子.
float a[4], b[4]
int i, c[4];
// assume that a contains 4.5 6.7 2.3 and 1.2
// assume that b contains 4.3 6.9 2.0 and 1.5
for (i = 0;i < 4; i )
c[i] = a[i] < b[i];
// take action on comparison result
在SSE指令集中,比较指令是cmpps,她有两个128位的操作数和一个选项参数.选项参数是用来指明指令比较类型的:是大于、小于、大于等于还是小于等于的比较.cmpps指令用来比较4对浮点数的大小,并将比较结果放在第一个操作数中.如果比较的结果是真,相对应的元素将被置为FFFFFFFF,如果为非,则被置为00000000.这个结果还可以被映射到一个普通的(8位或者16位)寄存器中,这个普通的寄存器用一个位来映射SSE寄存器的一个元素.我们可以从这个映射的寄存器来得到比较的结果.
下面举例来说明cmpps指令的用法.
; assume that xmm0 contains 4.5 6.7 2.3 and 1.2
; assume that xmm1 contains 4.3 6.9 2.0 and 1.5
; compare for less than condition
cmpps xmm0, xmm1, 1;
; move result of comparison as a mask into eax
movmskps eax, xmm0;
; test eax against some value
test eax, 5;
; jump if true, to the given label
je match13
这个操作也可以用图8的图示来表示.
图八 :比较运算
3.3 分支移除
通常,在程序中我们喜欢用下面的条件语句.
a =(a < b) ? c :d;
在上面的代码中,比较操作影响着后面代码的执行路径.如果我们能够移除条件判断,程序将能执行的更快.下面的这段汇编代码将比上面的代码执行得更快,这不仅仅是因为下面的代码是用汇编语言写的,更重要的是这里的分支判断已经被移除了.
; assume that xmm0 stores a, xmm1 stores b
; assume that xmm3 stores c and xmm4 stores d
cmpps xmm0, xmm1, 1;
movaps xmm2, xmm0;
andps xmm0, xmm3;
andnps xmm2, xmm4;
orps xmm0, xmm2;
; xmm0 contains the result, which is either c or d
转自:DDJ Microprocessor Center
下载本文示例代码