ZHCUAQ1G June 2015 – August 2025
向量数据对象:如果使用一元 & 运算符获取向量数据对象的地址,则结果为指针。与获取数组地址不同,获取向量的地址会提供一个表示整个向量的指针,而不是指向单个元素的指针。
例如,给定一个类型为 int4 的对象 vec,表达式 &vec 的类型为 int4 *。
int4 vec;
randomize(&vec); /* OK */
若要访问指针向量对象的元素,请使用混合运算符,而不是尝试将指针转换为其他某种指针类型。请参阅 节 7.15.3。
void randomize(int4 *vecp)
{
for([..]) (*vecp).s[i] = rand();
}
复杂数据对象:同样,如果使用一元 & 运算符获取复数标量对象的地址,则结果为指针。此指针表示整个复数标量对象,而不是单个组件。
cfloat cplx;
foo(&cplx); /* OK */
向量数据元素:您可以使用一元 & 运算符获取索引样式混合的地址(例如 &x.s[1] 或 x.s[j]))。地址是指向该元素的指针,带有预期的语义。但是,不能使用一元 & 运算符获取非索引样式混合的地址,例如 s1、s1() 或 r()。
带有 const 限定符的类型:您可以使用 const 类型限定符来声明向量类型和复数类型。其语义与非向量类型的语义相同。使用一元 & 运算符获取 const 向量对象的地址会给出指向符合 const 条件的向量类型的指针。
带有 volatile 限定符的类型:您可以使用 volatile 类型限定符来声明向量类型和复数类型。其语义与非向量类型的语义相同。使用一元 & 运算符获取 volatile 向量对象的地址会给出指向符合 volatile 条件的向量类型的指针。
在优化对任何 volatile 对象的访问时,编译器必须保留在运行时进行访问的次数和相对顺序。如果可能,编译器会一次访问 volatile 向量对象整个向量,而不是逐元素访问。如果这无法实现(例如,由于向量大于单个向量寄存器),编译器可以将 volatile 访问拆分为较小的块,并且未规定访问这些较小块的顺序。
应考虑这些循环。第一个循环是原始循环,第二个循环是向量化版本:
volatile int *p = array; /* original loop */
for ([..])
*p++ = [..] /* process one int at a time in sequence */
volatile int8 *p8 = (volatile int8 *)array; /* vectorized loop */
for ([..])
*p8++ = [..] /* process 8 int at a time in parallel */
此示例中的第一个循环按顺序一次访问一个数组元素。第二个循环并行访问 8 个 int。由于第二个循环会更改访问的相对顺序,因此不允许编译器自动将第一个循环转换为第二个循环。如果有必要,您可以手动从一种访问方式更改到另一种访问方式。
带有 restrict 限定符的指针:如果指针遵循 restrict 关键字的常规规则,则可以将 restrict 限定符添加到指向向量类型的指针。简而言之,指向向量的指针必须是访问指针指向的向量的唯一方式。
向量和数组之间的对应关系:向量最有效的用途是处理具有尽可能大向量的大量数据。向量和数组非常相似,因此您可以轻松使用向量指针来访问大型数组。例如:
int array[N];
int8 vec = *(int8 *)array; /* vector to access first 8 ints in array */
向量的一个重要用途是通过将数组(或指针)的地址转换为指向向量的指针类型来访问数组。然后,该指针可用于并行加载或存储数组的多个元素。假设您有以下示例:
void dotp(int x[N], int y[N], int z[restrict])
{
/* original */
for (int i=0;i<N;i++)
{
/* process the data one int at a time */
*z++ = *x++ + *y++;
}
}
编译器可以自动转换这个简单的例子,因为 restrict 关键字告诉编译器,z 输入参数不会与 x 或 y 重叠。
void dotp(int x[N], int y[N], int z[restrict])
{
int8 *xp = (int8 *)x;
int8 *yp = (int8 *)y;
int8 *zp = (int8 *)z;
for (int i=0;i<N/8;i++)
{
/* process the data 8 ints at a time */
*zp++ = *xp++ + *yp++;
}
}
const int array[N],则将向量设置为具有 int 元素的常量,如 const int8。使用指向向量的指针访问数组是安全的,因为数组和向量的元素在存储到存储器中时,以相同的方式存储和对齐。在存储器中,无论字节序模式如何,向量的第一个元素 (s0) 都存储在存储器中的最低地址。根据字节序模式来存储每个元素的各个字节。然后将数组的第一个元素分配给向量的第一个元素 (s0)。
使用 C++ 时,可以使用类似的代码来处理 std::vector 或 std::array 的多个元素,并使用 data() 成员:
void dotp(std::vector<int> x, std::vector<int> y, std::vector<int> z)
{
int8 *xp = (int8 *)x.data();
int8 *yp = (int8 *)y.data();
int8 *zp = (int8 *)z.data();
for (int i=0;i<N/8;i++)
{
/* process the data 8 ints at a time */
*zp++ = *xp++ + *yp++;
}
}
void dotp(std::array<int> x, std::array<int> y, std::array<int> z)
{
int8 *xp = (int8 *)x.data();
int8 *yp = (int8 *)y.data();
int8 *zp = (int8 *)z.data();
for (int i=0;i<N/8;i++)
{
/* process the data 8 ints at a time */
*zp++ = *xp++ + *yp++;
}
}
访问作为向量的复数类型:复数标量对象存储在存储器中,实分量位于最低地址。复数向量对象存储为复数标量值序列,s0 的实分量位于最低地址,后跟 s0 的虚分量,依此类推。
您可以将非复数数组作为复数标量来访问:
float value[2];
cfloat x = *(cfloat *)&value;
您可以将复数标量作为长度为 2 的复数标量来访问,该向量的元素类型与复数分量相同:
cchar value;
char2 x = *(char2 *)&cchar;
您可以将非复数标量数组作为复数向量来访问,前提是复数值存储在非复数标量数组中,首先存储实分量,然后存储虚分量。您可以根据需要使用一个 1 维或 2 维数组。
float value[2][] = { { r, i }, { r, i } ... };
cfloat4 x = *(cfloat4 *)&value;
float value[] = { r, i, r, i, ... };
cfloat4 x = *(cfloat4 *)&value;
您可以将任何复数向量作为两倍长度的向量来访问,并使用与复数分量相同的元素类型:
cchar4 value;
char8 x = *(char8 *)&value;
const int coefficients[2][N],则将向量设置为具有 int 元素的常量(例如 const cint8)。C 代码中复数值的一个常见用例是将它们表示为分量值的二维数组,如下所示:
float coefficients[2][4] =
{ { real, imag }, { real, imag },
{ real, imag }, { real, imag }, };
/* access part of the coefficient array as a vector */
output = input * *(cfloat4 *)coefficients;
您还可以从 C99 复数值数组中初始化复数向量,因为 C99 复数对象和 TI 复数对象的布局相同:
float _Complex coefficients[4] =
{ real + imag * _Complex_I, real + imag * _Complex_I,
real + imag * _Complex_I, real + imag * _Complex_I, };
/* access part of the coefficient array as a vector */
output = input * *(cfloat4 *)coefficients;