矢量拓展-使用GNU编译器gcc
来源
通过内置函数使用向量指令 。
本文主要为翻译。
6.52 通过内置函数使用向量指令
在某些目标上,指令集包含 SIMD 矢量指令 同时对一个大寄存器中包含的多个值进行操作。 例如,在 x86 上的 MMX、3DNow! 并且可以使用 SSE 扩展 这边走。
使用这些扩展的第一步是提供必要的数据 类型。 这应该使用适当的 typedef
:
typedef int v4si __attribute__ ((vector_size (16)));
这 int
type 指定 基本类型 ,而属性指定 变量的向量大小,以字节为单位。 例如, 上面的声明会导致编译器为 v4si
类型为 16 字节宽并分为 int
大小单位。 为了 一个 32 位 int
这意味着一个 4 个单位的 4 个字节的向量,并且 对应模式 foo
是 V4SI
。
这 vector_size
属性仅适用于积分和 浮动标量,尽管数组、指针和函数返回值 允许与此构造结合使用。 只有尺寸是 当前允许基本类型大小的 2 的正幂倍数。
所有基本整数类型都可以用作基类型,都可以作为有符号 并作为未签名: char
, short
, int
, long
, long long
. 此外, float
和 double
可 用于构建浮点向量类型。
指定对当前架构无效的组合 导致 GCC 使用更窄的模式合成指令。 例如,如果您指定一个类型为 V4SI
和你的 架构不允许这种特定的 SIMD 类型,GCC 产生使用 4 的代码 SIs
.
以这种方式定义的类型可以与普通 C 的子集一起使用 操作。 目前,GCC 允许使用以下运算符 在这些类型上: +, -, *, /, unary minus, ^, |, &, ~, %
.
操作的行为类似于 C++ valarrays
. 加法定义为 添加操作数的相应元素。 为了 例如,在下面的代码中,a
中的 4 个元素中的每一个都是添加到b
中相应的4个元素,结果向量存储在c
中。
typedef int v4si __attribute__ ((vector_size (16)));
v4si a, b, c;
c = a + b;
减法、乘法、除法和逻辑运算 以类似的方式操作。 同样,使用一元的结果 向量类型上的减号或补运算符是一个向量,其 元素是相应的负值或补充值 操作数中的元素。
可以使用移位运算符 <<
, >>
在 整数型向量。 操作定义如下: {a0, a1, …, an} >> {b0, b1, …, bn} == {a0 >> b0, a1 >> b1, …, an >> bn}
. 向量操作数必须具有相同数量的 元素。
为方便起见,允许使用二元向量运算 其中一个操作数是标量。 在这种情况下,编译器会转换 将标量操作数转换为向量,其中每个元素都是来自的标量 操作。 只有当标量可以是 安全地转换为向量元素类型。 考虑以下代码。
typedef int v4si __attribute__ ((vector_size (16)));
v4si a, b, c;
长 l;
a = b + 1; /* a = b + {1,1,1,1}; */
a = 2 * b; /* a = {2,2,2,2} * b; */
a = l + a; /* 错误,无法将 long 转换为 int。 */
向量可以下标,就好像向量是一个数组 相同数量的元素和基本类型。 越界访问 在运行时调用未定义的行为。 越界警告 可以启用矢量订阅访问 <samp>-Warray-bounds</samp> .
标准比较支持向量比较 运营商: ==, !=, <, <=, >, >=
. 比较操作数可以是 整数型或实型的向量表达式。 之间的比较 不支持整型向量和实型向量。 这 比较的结果是一个相同宽度和数量的向量 元素作为带有符号整数元素的比较操作数 类型。
当比较为假时,向量按元素进行比较,产生 0 和 -1(设置所有位的适当类型的常量) 否则。 考虑以下示例。
typedef int v4si __attribute__ ((vector_size (16)));
v4si a = {1,2,3,4};
v4si b = {3,2,1,4};
v4si c;
c = a > b; /* 结果将是 {0, 0,-1, 0} */
c = a == b; /* 结果将是 {0,-1, 0,-1} */
在 C++ 中,三元运算符 ?:
可用。 a?b:c
, 在哪里 b
和 c
是相同类型的向量,并且 a
是一个 具有相同大小的元素数量的整数向量 b
和 c
, 计算所有三个参数并创建一个向量 {a[0]?b[0]:c[0], a[1]?b[1]:c[1], …}
. 请注意,与 OpenCL, a
因此被解释为 a != 0
并不是 a < 0
. 与二元运算的情况一样,在以下情况下也接受此语法 之一 b
或者 c
是一个标量,然后转化为 向量。 如果两者 b
和 c
是标量和类型 true?b:c
具有与元素类型相同的大小 a
, 然后 b
和 c
被转换为向量类型,其元素具有 这种类型并且具有相同数量的元素 a
.
在 C++ 中,逻辑运算符 !, &&, ||
可用于向量。 !v
相当于 v == 0
, a && b
相当于 a!=0 & b!=0
和 a || b
相当于 a!=0 | b!=0
. 对于标量之间的混合操作 s
和一个向量 v
, s && v
相当于 s?v!=0:0
(评价是 短路)和 v && s
相当于 v!=0 & (s?-1:0)
.
可以使用函数进行矢量混洗 __builtin_shuffle (vec, mask)
和 __builtin_shuffle (vec0, vec1, mask)
. 这两个函数都构造了一个或两个元素的排列 向量并返回与输入向量类型相同的向量。 所述 掩模是具有相同宽度的积分矢量(W
) 和元素计数 (N
) 作为输出向量。
输入向量的元素按照vec0
从0
开始,vec1
从N
开始的内存顺序编号。mask
的元素在单操作数的情况下被视为模N
,在两操作数的情况下被视为模2*N
。
考虑下面的例子,
typedef int v4si __attribute__ ((vector_size (16)));
v4si a = {1,2,3,4};
v4si b = {5,6,7,8};
v4si mask1 = {0,1,1,3};
v4si mask2 = {0,4,2,5};
v4si res;
res = __builtin_shuffle (a, mask1); /* res 是 {1,2,2,4} */
res = __builtin_shuffle (a, b, mask2); /* res 是 {1,5,3,6} */
注意 __builtin_shuffle
在语义上是有意的 与 OpenCL 兼容 shuffle
和 shuffle2
职能。
您可以声明变量并在函数调用和返回中使用它们,如 以及在作业和一些演员。 您可以将向量类型指定为 函数的返回类型。 向量类型也可以用作函数 论据。 可以从一种向量类型转换为另一种向量类型, 只要它们的大小相同(实际上,您也可以投射向量 与其他相同大小的数据类型之间的往来)。
如果不进行强制转换,就不能在不同长度或不同符号的向量之间操作。
可以使用__builtin_shufflevector (vec1, vec2, index…)
函数进行向量变换。Vec1
和vec2
必须是具有兼容元素类型的vector
类型表达式。__builtin_shufflevector
的结果是一个元素类型与vec1
和vec2
相同的向量,但其元素计数等于指定的索引数。
索引参数是一个整数列表,指定应该在新向量中提取和返回的前两个向量的元素索引。这些元素索引按顺序编号,从第一个向量开始,一直到第二个向量。索引-1
可以用来表示返回的向量中对应的元素是一个无所谓的元素,可以自由选择,以优化执行shuffle
操作所生成的代码序列。
考虑下面的例子,
typedef int v4si __attribute__ ((vector_size (16)));
typedef int v8si __attribute__ ((vector_size (32)));
v8si a = {1,-2,3,-4,5,-6,7,-8};
v4si b = __builtin_shufflevector (a, a, 0, 2, 4, 6); /* b 是 {1,3,5,7} */
v4si c = {-2,-4,-6,-8};
v8si d = __builtin_shufflevector (c, b, 4, 0, 5, 1, 6, 2, 7, 3); /* d 是一个 */
可以使用__builtin_convertvector (vec, vectype)
函数进行向量转换。Vec
必须是具有整型或浮点型vector
类型的表达式,而vectype
必须是具有相同元素数的整型或浮点型vector
类型。结果具有vectype
类型和vec
中每个元素的C
转换值,它们都是vectype
的元素类型。
考虑下面的例子,
typedef int v4si __attribute__ ((vector_size (16)));
typedef float v4sf __attribute__ ((vector_size (16)));
typedef double v4df __attribute__ ((vector_size (32)));
typedef unsigned long long v4di __attribute__ ((vector_size (32)));
v4si a = {1,-2,3,-4};
v4sf b = {1.5f,-2.5f,3.f,7.f};
v4di c = {1ULL,5ULL,0ULL,10ULL};
v4sf d = __builtin_convertvector (a, v4sf); /* d 是 {1.f,-2.f,3.f,-4.f} */
/* 相当于:
v4sf d = { (float)a[0], (float)a[1], (float)a[2], (float)a[3] }; */
v4df e = __builtin_convertvector (a, v4df); /* e 是 {1.,-2.,3.,-4.} */
v4df f = __builtin_convertvector (b, v4df); /* f 是 {1.5,-2.5,3.,7.} */
v4si g = __builtin_convertvector (f, v4si); /* g 是 {1,-2,3,7} */
v4si h = __builtin_convertvector (c, v4si); /* h 是 {1,5,0,10} */
有时需要混合使用通用向量来编写代码 操作(为了清晰起见)和机器特定的向量内在函数(为了 访问未通过通用内置程序公开的向量指令)。 在 x86 上,整数向量的内在函数通常使用相同的 矢量类型 __m128i
不管他们如何解释向量, 使得有必要转换它们的参数并从/到返回值 其他向量类型。 在 C 中,您可以使用 union
类型:
#include <immintrin.h>
typedef unsigned char u8x16 __attribute__ ((vector_size (16)));
typedef unsigned int u32x4 __attribute__ ((vector_size (16)));
typedef union{
__m128i mm;
u8x16 u8;
u32x4 u32;
} v128;
对于可以与内置运算符和 x86 一起使用的变量 内在函数:
v128 x, y = { 0 };
memcpy (&x, ptr, sizeof x);
y.u8 += 0x80;
x.mm = _mm_adds_epu8 (x.mm, y.mm);
x.u32 &= 0xffffff;
/* 复合字面值可以用来将内部调用的返回值传递给期望使用并集的函数,而不是变量:*/
v128 foo (v128);
x = foo ((v128) {_mm_adds_epu8 (x.mm, y.mm)});