关于SIMD指令的一些问题

  • 今天看CSAPP,看到关于SIMD指令的东西,所以在网上找了点资料.
    资料来源于
    1)
    上交精品课
    2)
    以 XL C/C++ 编译器为例使用向量技术编程
    如果侵权,请联系我删除

  • 以下简单的贴一下内容

单指令流多数据流(SIMD)是一种实现数据级并行的技术,其典型代表是向量处理器(Vector Processor)和阵列处理器(Array Processor)。

SIMD技术最初主要应用在大规模的超级计算机中,但是近些年来,小规模SIMD技术也开始在个人计算机上得到广泛应用。

SIMD技术的关键是在1条单独的指令中同时执行多个运算操作,以增加处理器的吞吐量。为此,SIMD结构的CPU有多个执行部件,但都在同一个指令部件的控制之下,中央控制器向各个处理单元发送指令,整个系统只要求有一个中央控制器,只要求存储一份程序,所有的计算都是同步的。

为了了解SIMD在性能上的优势,我们以加法指令为例进行说明:

单指令流单数据流(SISD)型CPU对加法指令译码后,执行部件先访问主存,取得第一个操作数,之后再一次访问主存,取得第二个操作数,随后才能进行求和运算;而在SIMD型CPU中,指令译码后,几个执行部件同时访问主存,一次性获得所有操作数进行运算。

这一特点使得SIMD技术特别适合于多媒体应用等数据密集型运算。

1.MMX技术

MMX(Multi-Media Extension,多媒体扩展)是Intel设计的一种SIMD多媒体指令集。作为一种多媒体扩展技术,MMX大大提高了计算机在多媒体和通信应用方面的能力,带有MMX技术的CPU适合于数据量很大的图形、图像数据处理,从而使三维图形、动画、视频、音乐合成、语音识别、虚拟现实等数据处理的速度有了很大提高。

MMX技术的优点是增加了多媒体处理能力,可以一次处理多个数据,缺点则是仅仅只能处理整型数,并且由于占用浮点数寄存器进行运算,以至于MMX指令集与x87浮点运算指令不能够同时执行,必须做密集的切换才可以正常执行,这种情况势必造成整个系统运行质量的下降。

2.SSE技术

1999年,Intel在其Pentium III微处理器中集成了SSE(Streaming SIMD Extensions)技术,有效增强了CPU浮点运算的能力。

SSE兼容MMX指令,可以通过SIMD和单时钟周期并行处理多个浮点数据来有效提高浮点运算速度,对图像处理、浮点运算、3D运算、视频处理、音频处理等诸多多媒体应用起到全面强化作用。

具有SSE指令集支持的处理器有8个128位的寄存器,每一个寄存器可以存放4个单精度(32位)浮点数。SSE同时提供了一个指令集,其中的指令允许把浮点数加载到这些128位寄存器中,这些数就可以在这些寄存器中进行算术逻辑运算,然后把结果送回主存。也就是说,SSE中的所有计算都可以针对4个浮点数一次性完成,这种批处理带来了效率的提升。

例如,考虑下面这个任务:计算一个很长的浮点型数组中每一个元素的平方根。

实现这个任务的算法一般可以写为:

for each f in array

{

把f从主存加载到浮点寄存器

计算平方根

再把计算结果从寄存器中取出写入主存

}

而在采用SSE技术后,算法可以改写为:

for each 4 members in array //对数组中的每4个元素

{

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

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

把所得的4个结果取出写入主存

}

3.SSE2技术

2001年,Intel配合其Pentium 4微处理器,推出了SSE2(Streaming SIMD Extensions 2)指令集,扩展了SSE指令集,并可完全取代MMX。

SSE2指令集是Intel公司在SSE指令集的基础上发展起来的。相比于SSE,SSE2使用了144个新增指令,扩展了MMX技术和SSE技术,提高了诸如MPEG-2、MP3、3D图形等应用程序的运行性能。

在整数处理方面,随MMX技术引进的SIMD整数指令从64位扩展到了128 位,使SIMD整数类型操作的执行效率成倍提高;在浮点数处理方面,双精度浮点SIMD指令允许以 SIMD格式同时执行两个浮点操作,提供双精度操作支持有助于加速内容创建、财务、工程和科学应用。除SSE2指令之外,最初的SSE指令也得到增强,通过支持多种数据类型(例如双字、四字)的算术运算,支持灵活、动态范围更广的计算功能。

4.SSE3技术

2004年,Intel在其基于Prescott核心的新款Pentium 4处理器中,开始使用SSE3(Streaming SIMD Extensions 3)技术。

SSE3指令集是Intel公司在SSE2指令集的基础上发展起来的。相比于SSE2,SSE3在SSE2的基础上又增加了13条SIMD指令,以提升Intel超线程(Hyper-Threading)技术的效能,最终达到提升多媒体和游戏性能的目的。

  • 然后是<<以 XL C/C++ 编译器为例使用向量技术编程>>的内容

向量技术介绍

单指令流多数据流(SIMD,即 Single Instruction, Multiple Data)是一种采用一个控制器来控制多个处理器,同时对一组数据中的每一个分别执行相同的操作从而实现空间上的并行性的技术。这里提到的一组数据又称向量。

AltiVec 是由 Apple、IBM、Motorola PowerPC 联合开发的单指令多数据指令集,每个公司使用不同的名字在市场上进行销售。IBM 把对 AltiVec 的实现和扩展称为 VMX,即 Vector/SIMD Multimedia Extension,而后又进行了进一步扩展推出了 VSX,即 Vector Scalar Extension。

使用向量技术能够获得更快的执行速度,对于类似多媒体这种大量使用算法的应用中效果尤其显著。

开发者在使用 XL C, XL C/C++和 XL Fortran 等编译器时可以使用向量技术。本文主要以 XL C/C++ 编译器为例介绍如何使用向量技术。

回页首
支持向量技术的 POWER 处理器

单指令流多数据流是由处理器支持的,IBM 的 POWER 系列处理器自 POWER6 开始支持 VMX 向量技术,并随后扩展到了 VSX,详见表 1。
表 1.POWER 架构支持的向量技术
架构 支持的向量技术
POWER5 N/A
POWER6 VMX
POWER6 in enhanced mode VMX
POWER7 VMX, VSX
POWER8 VMX, VSX

  • 如何使用向量技术
    手动编码
    必要条件

使用向量技术的必要条件是在编译时指明以下两个编译选项:

使用-qarch 编译选项,而且必须指向某个支持向量技术的处理器架构,如-qarch=pwr7,这就要求编译环境中处理器本身支持向量技术。开发者可以使用 less /proc/cpuinfo 命令在 Linux 平台查询处理器型号。

使用-qaltivec 编译选项,否则编译器无法识别下文提到的向量类型和向量操作符。

向量类型

用户可以使用 vector 或_vector 关键字再加上要声明的变量类型来声明向量类型,例如 vector signed char。除了计算机的基本变量类型,例如 signed char,还可以使用 pixel 类型。pixel 是向量技术中的一个新的关键字,一个 pixel 变量占用 2 字节,并分为 4 部分,第一部分占 1 比特,其余三部分分别占 5 比特。

所有向量类型都占 16 字节,根据其变量类型不同,每种向量可能包括 2,4,6,8,16 个元素。例如,每个 unsigned char 和 signed char 占 1 字节,所以 vector unsigned char 和 vector signed char 包含 16 个对应的基本类型的变量;而每个 unsigned int 和 signed int 占 4 字节,所以 vector unsigned int 和 vector signed int 包含 4 个对应的基本类型的变量。

表 2.向量类型对应的基本类型
向量类型 包含内容
vector unsigned char 16 unsigned char
vector signed char 16 signed char
vector bool char 16 unsigned char
vector unsigned short 8 unsigned short
vector unsigned short int
vector signed short 8 signed short
vector signed short int
vector bool short 8 unsigned short
vector bool short int
vector unsigned int 4 unsigned int
vector unsigned long
vector unsigned long int
vector signed int 4 signed int
vector signed long
vector signed long int
vector bool int 4 unsigned int
vector bool long
vector bool long int
vector unsigned long long 2 unsigned long long
vector bool long long
vector signed long long 2 signed long long
vector float 4 float
vector double 2 double
vector pixel 8 unsigned short

对于上述的某些类型,如 vector unsigned long long, vector bool long long, vector signed long long 和 vector double,处理器必须支持 VSX,如 POWER7 及以上处理器。

  • 初始化

开发者可以通过向量常量(vector literal)对向量类型进行初始化。向量常量的类型就是第一个括号中的向量类型,其值是第二个括号中一系列的常量表达式表示(如果只有一个常量表达式,则所有向量元素都被赋为该值)。除此之外,还可以使用其他已赋值的向量进行赋值。参考例 1。

而对于 C 的程序,开发者还可以用初始化列表。初始化列表以花括号为界,列表中值的个数必须小于或等于向量类型中的元素个数。未指明的元素被初始化为 0。参考例 2。

操作符

总体而言,向量类型支持以下适用于基本类型的操作符。

一元操作符:++,–,+,-,~

二元操作符:+,-,*,/,%,&,|,^,<<,>>,[]

关系操作符:==,!=,<,>,<=,>=

然而,具体的支持情况由向量类型决定,如 bool vector 就不支持加减乘除或大于小于等操作符。

这里特别要说明的是下标运算符[]是可以用于向量类型操作的,这让向量类型更像传统意义上的数组。 通过下标运算符就能选中向量中的某个元素。如果所指定的位置超出了有效区间,则结果是未定义的。参考例 1 和例 2 对[]操作符的运用。
清单 1

#include <stdio.h>
int main()
{
vector unsigned int v1 = (vector unsigned int)(5,10,15,20);
vector unsigned int v2 = (vector unsigned int)(10);
vector unsigned int v3 = v2;
printf("%i,%i,%i,%i\n",v1[0],v1[1],v1[2],v1[3]);
printf("%i,%i,%i,%i\n",v2[0],v2[1],v2[2],v2[3]);
printf("%i,%i,%i,%i\n",v3[0],v3[1],v3[2],v3[3]);
return 0;
}

结果:

5,10,15,20
10,10,10,10
10,10,10,10

清单 2

#include <stdio.h>
int main()
{
int i;
vector unsigned int v2 = {1,2,3,4};
for (i=0;i<4;i++)
printf("%i\n",v2[i]);
return 0;
}

结果:

1
2
3
4

内置向量函数

开发者可以利用内置的向量函数来操作向量类型的元素,比如对多个向量进行运算、比较两个向量、将向量从内存中加载到寄存器中、将寄存器中的向量储存到内存中。

除了支持 AltiVec Technology Programming Interface Manual 中提及的函数,IBM 的 XL 编译器还进行了扩展, 提供了更多的函数来更有效操作向量。开发者可以在程序中直接调用这些向量函数。

参考例 3 中对 vec_cmpeq()函数的使用。
清单 3

int main()
{
int i;
vector unsigned char v1 = 
{'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p'};
vector unsigned char v2 = (vector unsigned char) 'a';
vector bool char v3;
v3 = vec_cmpeq(v1,v2);
for (i=0;i<16;i++)
printf("%i\n",v3[i]);
return 0;
}

结果:

255
0
0
0
0
...

除了上文提到的[ ]操作符来对向量内元素进行操作,还可以通过以下访问某个指定的向量元素并进行操作。

d=vec_extract(a, b) 返回 vector b 中的第 a 个元素
d=vec_insert(a, b, c) 把 vector b 中的第 c 个元素换成 a 值
d=vec_promote(a, b) 返回元素 b 为 a 值的 vector,其余元素都未定义
d=vec_splats(a) 返回所有元素均为 a 值的 vector

开发者可以通过编译器的用户文档了解向量函数的更多内容。

  • 库函数

IBM 的 Mathematical Acceleration Subsystem(数学加速子系统,简称 MASS)包括了一系列针对 IBM 特定处理器调优的数学函数,搭载于在 XL C, XL C/C++ 和 XL Fortran 编译器中。MASS 库函数提供了比标准数学库函数更优化的性能。具体而言,MASS 库包括了标量 C/C++函数库,向量函数库和 SIMD 函数库。

用户可以显式调用这些函数,步骤如下:

在程序中使用 MASS 库函数。

了解库函数的命名规则有助于开发者使用这些函数。

对于双精度函数:标量函数库沿用了函数基础名称,向量函数库在函数基础名称上加了 v 前缀,而 SIMD 函数库在函数基础名称上加了 d2 后缀。

对于单精度函数:标量函数库在函数基础名称上加了 f 后缀,向量函数库在函数基础名称上加了 vs 前缀,而 SIMD 函数库在函数基础名称上加了 f4 后缀。

我们以 acos()函数为例。acos(x)函数返回 x 的 acosin 值。
表 3.acos 函数对应的 MASS 标量函数,MASS 向量函数和 MASS SIMD 函数
双精度 单精度
标量函数 acos() acosf()
向量函数 vacos() vsacos()
SIMD 函数 acosd2() acosf4()

在程序中包含对应的头文件。

在编译时链接对应的库。例如如果需要链接 libmass.a,则编译时还需要在命令行再输入 -lmass 选项。

以 XL C/C++ for Linux, 13.1 对应的头文件和库为例:
表 4.XL C/C++ for Linux, 13.1 中 MASS 函数库对应的头文件和库文件
头文件名称 库名称及说明
标量函数库 math.h libmass.a 针对 32 位处理器
libmass_64.a 针对 64 位处理器
向量函数库 mathv.h libmassvp5.a 已针对 POWER5 调优
libmassvp6.a 已针对 POWER6 调优
libmassvp7.a 已针对 POWER7 调优
libmassvp8.a 已针对 POWER8 调优
SIMD 函数库 math_simd.h libmass_simdp7.a 针对 32 位 POWER7 处理器并调优
libmass_simdp7_64.a 针对 64 位 POWER7 处理器并调优
libmass_simdp8.a 针对 32 位 POWER8 处理器并调优
libmass_simdp8_64.a 针对 64 位 POWER7 处理器并调优

好处

和下文提到的代码自动向量化,开发者在代码中直接使用向量技术能精准地利用向量技术的优势,而且通常能避免无法预料的结果。但是手动使用向量技术编程对开发者要求较高,而且可能需要系统性地进行代码改造。
自动向量化

除了手动编程方法,开发者还可以在编译时启用对应的编译选项将程序自动向量化。

-qsimd

-qsimd 控制了编译器是否能自动利用处理器所支持的向量指令。

-qsimd=auto 将某些循环中的对数组中连续元素的操作转化为 VMX 或 VSX 指令。向量指令能一次性计算多个结果,比顺序执行要快。这对于含有大量图像处理的程序非常有用。

-qhot=vector

当-qhot=vector 生效时,对于某些对于数组中连续元素的循环操作,如求平方根或反平方根,编译器会调用 MASS 库函数中对应的函数。-qhot=vector 支持单精度和双精度浮点数学运算,所以对于需要处理大量数学运算的程序非常有用。

当-qhot=vector 生效时,在程序调用系统数学函数时,编译器通过衡量函数名、调用次数等确定是否进行自动向量化,如果是,则转化为调用对应的 MASS 向量函数,某些函数如 vdnit,vdint,vcosisin 除外。如果无法向量化,编译器会尝试调用对应的 MASS SIMD 函数。

《怎样提高调用数学函数的程序的性能》介绍了如何通过自动向量化使用 MASS 高性能数学库,并展示了加速后的效果。

需要注意的是,由于向量化可能影响程序结果的精确性,如果这种精确性偏差超出了接受范围,则开发者可以用-qhot=novector 来关闭上述操作。

-O 优化选项

除了由开发者显示开启编译选项,XL 编译器将一些有利于提高性能的优化操作打包起来,提供从 O2 到 O5 的预定义优化等级,供开发者在不熟悉如何组合各种编译选项来提高性能时使用。例如,-O4 和-O5 优化等级包含了-qhot=vector 和-qsimd=auto;对于 POWER7 及以上的处理器,-O3 -qhot 优化等级已包含了-qsimd=auto。

通常,更高的优化等级是在其上一个优化等级的基础上新增了其它编译选项。

当以下编译选项或编译选项组合生效时,-qhot=vector 也被启用。

    -qhot -qnostrict -qignerrno     -qhot -O3     -O4     -O5

回页首
字节顺序

字节顺序是指当数据存储跨多个字节时最高有效字节和最低有效字节在内存中的存放方式。在大端模式下,数据的最高有效字节存放在低地址;在小端模式下,数据的最低有效字节存放在低地址。例如一个跨 4 个字节的 32 位 int 型数 0x12345678,其最高有效字节是 0x12,最低有效字节是 0x78,它在 CPU 内存中有两种存放方式,此处假设从地址 0x0000 开始存放:
表 5.内存存放布局(方式 1:大端模式 )
地址 0x0000 0x0001 0x0002 0x0003
存放内容 0x12 0x34 0x56 0x78
表 6.内存存放布局(方式 2:小端模式 )
地址 0x0000 0x0001 0x0002 0x0003
存放内容 0x78 0x56 0x34 0x12

方式 1 的存放形似称为大端模式,最高有效字节 0x12 存放在低地址中,方式 2 的存放形似称为小端模式,最低有效字节 0x78 存放在低地址中。

这两种模式并没有本质上的区别,大多数开发者并不用关心具体的细节。但如果涉及代码在两种模式下移植时,就需要格外注意。

常见的大端模式的机型有:IBM Power 系列、IBM z 系列

常见的小端模式的机型有:Intel x86 系列

值得一提的是,IBM Power8 既支持大端模式,也支持小端模式。

回页首
字节顺序影响向量存储

由于所有向量变量都占 16 字节并以 16 字节对齐,这决定了在内存中是向量是跨字节存储的。向量的存储在大端模式下和小端模式下不同,让我们来看一个简单的例子:

初始化向量 v,v 含有四个元素:
例 4

vector unsigned int v = {1,2,3,4};

向量元素在内存中存放布局如图 2 所示:
表 7.向量元素在内存中的存放布局(大端模式)
地址 ADD ADD+0x1 ADD+0x2 ADD+0x3
向量元素 v[0] v[1] v[2] v[3]
元素值 1 2 3 4
表 8.向量元素在内存中的存放布局(小端模式)
地址 ADD ADD+0x1 ADD+0x2 ADD+0x3
向量元素 v[3] v[2] v[1] v[0]
元素值 4 3 2 1

若程序需要在大端模式和小端模式间进行移植,开发者需要关注字节顺序不同带来的变化,可能需要进行某些转换以保证数据的正确性。总体而言,字节存储顺序对大多数向量函数并没有影响,比如当使用 vec_add(a,b)求向量 a 和向量 b 对应元素的和,在大端模式和小端模式上并无区别。然而,以下三类函数会受到向量元素顺序的影响:

加载函数

vec_xl(a, b):从指针 b 加上 a 偏移量处开始加载一个向量
vec_ld(a, b):从指针 b 加上 a 偏移量后截取 16 的倍数开始加载一个向量
vec_xld2(a, b):从指针 b 加上 a 偏移量处开始加载由两个 8 字节的元素合成的一个向量
vec_xlw4(a, b):从指针 b 加上 a 偏移量处开始加载由四个 4 字节的元素合成的一个向量

存储函数
vec_xst(a, b, c):向指针 c 加上 b 偏移量处存储向量 a
vec_st(a, b, c):向指针 c 加上 b 偏移量后截取 16 的倍数处存储向量 a
vec_xstd2(a, b, c):向指针 c 加上 b 偏移量处按照 a 向量的值存储两个 8 字节的元素
vec_xstw4(a, b, c):向指针 c 加上 b 偏移量处按照 a 向量的值存储四个 4 字节的元素

指向某个向量元素的函数
vec_extract(a, b):返回向量 b 中第 a 个元素的值
vec_insert(a, b, c):复制向量 b,并将其第 c 个元素的值变为 a
vec_mergeh(a, b):合并向量 a 和向量 b 的最高 8 字节后成为的向量
vec_mergel(a, b):合并向量 a 和向量 b 的最低 8 字节后成为的向量
vec_pack(a, b): 截取向量 a 和向量 b 每个元素的低位并合后成为的向量
vec_promote(a, b):返回一个向量,其第 b 个元素的值是 a
vec_splat(a, b): 返回一个向量,其所有元素的值都等于向量 a 的第 b 个元素
vec_unpackh(a):返回一个向量,其每个元素都是向量 a 中对应元素的最高 8 字节
vec_unpackl(a):返回一个向量,其每个元素都是向量 a 中对应元素的最低 8 字节
vec_perm(a, b, c):按照字符向量 c 所指明的元素下标选择向量 a 和向量 b 中的元素,返回一个向量

  • 从大端模式向小端模式移植带有向量的代码

现在,小端模式有着越来越好的生态圈,IBM 针对 Linux 也推出了小端模式,开发者可能希望把已有的基于大端模式的程序移植到小端模式平台。为了让开发者花最小的代价进行这种移植,XL C/C++ for Linux (little-endian distributions), 13.1.1 版本编译器新增了一个编译选项-qaltivec=be。

-qaltivec=be 表示内存中向量元素的顺序遵循大端模式。在移植遵循大端模式的已有代码时,这个编译选项不改变向量元素在内存中存储的字节顺序,即内存依然沿用移植前的大端模式,即对例 4 中向量 v 的存储应如图 3 所示,只是在调用上述提到的与字节顺序有关的向量函数时,对向量元素顺序进行处理以符合现在的小端模式环境。在移植后,现在的编译环境是小端模式,即向量元素从高地址向低地址依次排列,在-qaltivec=be 编译选项生效时:

  • 加载函数

使用 vec_ld(), vec_xld2(), vec_xlw4(), vec_xl()把数据从内存加载到向量寄存器时,编译器会逆转向量元素的顺序,即寄存器中向量会以小端模式储存,符合处理器的小端模式字节顺序。以图 4 为例。
表 9.内存中向量存储(内存(移植前的大端模式))
地址 ADD1 ADD1+0x1 ADD1+0x2 ADD1+0x3

向量元素 v[0] v[1] v[2] v[3]
元素值 1 2 3 4

<<<<<<<<<<<<<<<<<小端模式下字节顺序<<<<<<<<<<<<<<<<<<<<<<<

移植到小端模式平台后,假设寄存器直接沿用以上布局,按照小端模式字节顺序,向量元素排序变成 4,3,2,1,与程序意图不符。

启用-qaltivec=be 后,编译器会逆转向量元素的顺序,即寄存器中向量会以小端模式储存,寄存器的布局变为图 4 所示:
表 10.-qaltivec=be 下寄存器中向量存储

向量元素 v[0] v[1] v[2] v[3]
元素值 4 3 2 1

<<<<<<<<<<<<<<<<<小端模式下字节顺序<<<<<<<<<<<<<<<<<<<<<<<

我们看到启用-qaltivec=be 后寄存器的布局向量元素的顺序发生了逆转,按照小端模式,向量元素排列变成 1,2,3,4,符合程序意图。

存储函数

使用 vec_st(), vec_xstd2(), vec_xstw4(), vec_xst()把数据从向量寄存器储存到内存时,编译器会逆转向量元素的顺序,即内存中向量会以大端模式储存,即从图 5 向图 6 进行转换,从而符合内存中原本的大端模式字节顺序。
表 11.寄存器中向量存储

向量元素 v[0] v[1] v[2] v[3]
元素值 4 3 2 1

表 12.-qaltivec=be 下内存中向量存储

地址 ADD2 ADD2+0x1 ADD2+0x2 ADD2+0x3
向量元素 v[0] v[1] v[2]
元素值 1 2 3

‘>>>>>>>>>>>>>>>大端模式下字节顺序>>>>>>>>>>>>>>>>>>

指向某个向量元素的函数

对于 vec_extract(), vec_insert(), vec_mergeh(), vec_mergel(), vec_pack(), vec_promote(), vec_splat(), vec_unpackh(), vec_unpackl(), vec_erm(),编译器会以大端模式字节顺序计算元素位置,使之符合向量元素的正确顺序。

-qaltivec=be 只能影响上述提到的向量函数,对于用 union 初始化向量的情况不能产生影响,参考例 5,在小端模式下 union 初始化向量始终按照小端模式。开发者应该用 vec_xl 或 vec_ld 加载函数替代 union,以保证与后续操作一致,即同时受-qaltivec=be 影响。
清单 5

int main()
{
union { 
vector signed int a4;
int c4[4];
} x;
x.c4[0] = 0;
x.c4[1] = 1;
x.c4[2] = 2;
x.c4[3] = 3;
}

对于在小端模式下新开发的程序,开发者应该直接使用默认的小端模式,即使用-qaltivec 或-maltivec 编译选项,其默认值就是符合小端模式,开发者不要使用以上提到的-qaltivec=be 进行任何转换,否则可能出现错误,参考例 6。
清单 6

#include <stdio.h>
int main()
{
vector signed int a4;
int c4[4] = {0,1,2,3};
int i;
a4 = vec_xlw4(0, c4);
//vec_xlw4 将会受到 -qaltivec=be|le 影响
printf("%i %i %i %i\n", a4[0], a4[1], a4[2], a4[3]);
//在小端模式下,[]操作符不受-qaltivec=be|le 影响,向量元素的始终从高地址向低地址依次排列
for(i=0;i<4;i++)
printf("%i ", vec_extract(a4,i));
//vec_extract 将会受到 -qaltivec=be|le 影响
printf("\n");
}

这里我们使用的是 POWER7 小端模式处理器,分别使用-qaltivec=be 和-qaltivec(等同于-qaltivec=le)进行编译,结果如下:

xlc altivec_1.c -qaltivec=be -qarch=pwr7
a.out
3 2 1 0
0 1 2 3
xlc altivec_1.c -qaltivec -qarch=pwr7
a.out
0 1 2 3
0 1 2 3

我们可以看到启用-qaltivec=be 后打印出的第一行的向量元素顺序被颠倒了。这是因为 vec_xlw4 受-qaltivec=be 影响,在把向量从内存加载到寄存器时逆转了向量元素顺序,按照大端模式进行存储,但是由于[]操作符不受-qaltivec=be 影响,始终按照小端模式计算向量元素位置,所以打印时向量元素顺序被颠倒了,与程序意图不符。

启用-qaltivec=be 后打印出的第二行的向量元素顺序正确,这是因为 vec_extract 和 vec_xlw4 一样受-qaltivec=be 影响,在计算向量元素位置时也是按照大端模式,相当于“负负得正”,也符合了程序的意图。

在编程时开发者可能不会意识到[]操作符和 vec_extract()的在启用-qaltivec=be 时存在的差别,最好的办法就是在小端模式下遵循-qaltivec 的默认值,始终可以得到正确的结果,如例 6 的编译结果所示。

对于 XL C/C++ for Linux (little-endian distributions), 13.1.1 版本,开发者只能使用-qaltivec 来开启 be 子选项,以后其 GCC 对应的选项-maltivec 也会新增 be 子选项,方便开发者使用。

你可能感兴趣的:(CSAPP)