我使用GNU C向量扩展,而不是英特尔的_mm_*
intrinsic。
我想做与英特尔的_m256_loadu_pd
内在相同的事情。一个接一个赋值很慢:gcc生成的代码有4条加载指令,而不是一个单独的vmovupd
(_m256_loadu_pd
确实生成)。
typedef double vector __attribute__((vector_size(4 * sizeof(double))));
int main(int argc, char **argv) {
double a[4] = {1.0, 2.0, 3.0, 4.0};
vector v;
/* I currently do this */
v[0] = a[0];
v[1] = a[1];
v[2] = a[2];
v[3] = a[3];
}
我想要这样的东西:
v = (vector)(a);
或
v = *((vector*)(a));
,但都不起作用。第一个以"无法将值转换为向量"失败,而第二个则导致段错误。
update:我看到你使用的是GNU C的原生向量语法,而不是Intel的intrinsic。您是否为了可移植到非x86而避免使用Intel的固有特性?gcc目前在编译使用GNU C向量的代码时做得很差,超出了目标机器所支持的范围。(您可能希望它只使用两个128b向量并分别对每个向量进行操作,但显然情况比这更糟。)
无论如何,这个答案展示了如何使用Intel x86 intrinsic将数据加载到GNU C向量语法类型
首先,如果你想学习什么可以编译成好的代码,看看编译器的输出小于-O2
是浪费时间。您的main()
将在-O2下优化为ret
。
除此之外,每次给一个vector对象赋值会得到不好的asm也就不足为奇了。
旁白:一般人会称其为v4df
(vector of 4 Double Float)或其他类型,而不是vector
,所以当他们在c++ std::vector
中使用它时不会发疯。对于单精度,v8sf
。IIRC, gcc在__m256d
内部使用这样的类型名称。
在x86上,Intel的内在类型(如__m256d
)是在GNU C向量语法之上实现的(这就是为什么你可以在GNU C中做v1 * v2
而不是写_mm256_mul_pd(v1, v2)
)。您可以自由地从__m256d
转换为v4df
,就像我在这里所做的那样。
我已经在函数中包装了这两种相同的方法,所以我们可以看看它们的asm。请注意,我们没有从同一个函数中定义的数组中加载,因此编译器不会将其优化掉。
我把它们放在Godbolt编译器资源管理器上,这样你就可以用各种编译选项和编译器版本查看asm。
typedef double v4df __attribute__((vector_size(4 * sizeof(double))));
#include <immintrin.h>
// note the return types. gcc6.1 compiles with no warnings, even at -Wall -Wextra
v4df load_4_doubles_intel(const double *p) { return _mm256_loadu_pd(p); }
vmovupd ymm0, YMMWORD PTR [rdi] # tmp89,* p
ret
v4df avx_constant() { return _mm256_setr_pd( 1.0, 2.0, 3.0, 4.0 ); }
vmovapd ymm0, YMMWORD PTR .LC0[rip]
ret
如果_mm_set*
intrinsic的参数不是编译时常量,编译器将尽其所能编写有效的代码,将所有元素放入单个vector中。通常最好这样做,而不是编写将存储到tmp数组并从中加载的C语言,因为这并不总是最好的策略。多个窄存储转发失败,转发到宽负载时,除了通常的存储转发延迟外,还需要额外的10个周期(IIRC)的延迟。如果您的double
已经在寄存器中,通常最好只是将它们洗牌在一起。
参见是否有可能将浮点数直接转换为__m128,如果它们是16字节分配?获取将单个标量转化为向量的各种本征函数的列表。x86标签wiki有Intel手册的链接,以及它们的内在查找器。
加载/存储没有Intel intrinsic的GNU C vector:
我不知道你"应该"怎么做。这个Q&A建议转换一个指向你想要加载的内存的指针,并使用像typedef char __attribute__ ((vector_size (16),aligned (1))) unaligned_byte16;
这样的向量类型(注意aligned(1)
属性)。
你从*(v4df *)a
得到一个段错误,因为a
可能没有在32字节的边界上对齐,但是你使用的是一个假设自然对齐的向量类型。(就像__m256d
一样,如果你对指向它的指针解引用,而不是使用load/store内部函数向编译器传达对齐信息。)
你可以在x86上使用gcc的等效内部函数:__builtin_ia32_loadupd256 (https://gcc.gnu.org/onlinedocs/gcc/x86-Built-in-Functions.html#x86-Built-in-Functions)。
比如:
typedef double v4df __attribute__((vector_size(4 * sizeof(double))));
void vector_copy(double *a, v4df *v)
{
*v = __builtin_ia32_loadupd256(a);
}
如果您不需要获取a的副本,则使用指针代替(参见示例中的v_ptr)。如果需要拷贝,使用memmove(参见v_copy)
#include <stdio.h>
#include <string.h>
typedef double vector __attribute__((vector_size(4 * sizeof(double))));
int main(int argc, char **argv) {
double a[4] = {1.0, 2.0, 3.0, 4.0};
vector *v_ptr;
vector v_copy;
v_ptr = (vector*)&a;
memmove(&v_copy, a, sizeof(a));
printf("a[0] = %f // v[0] = %f // v_copy[0] = %fn", a[0], (*v_ptr)[0], v_copy[0]);
printf("a[2] = %f // v[2] = %f // v_copy[0] = %fn", a[2], (*v_ptr)[2], v_copy[2]);
return 0;
}
输出:a[0] = 1.000000 // v[0] = 1.000000 // v_copy[0] = 1.000000
a[2] = 3.000000 // v[2] = 3.000000 // v_copy[0] = 3.000000