如何使用GNU C Vector Extensions从/向数组加载/存储?

我正在使用GNU C Vector Extensions ,而不是Intel的_mm_*内在函数。

我想做与英特尔的_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)); 

但都没有工作。 第一个失败,“无法将值转换为向量”,而第二个导致段错误。

更新:我看到你使用的是GNU C的本机矢量语法,而不是Intel内在函数。 您是否因为非x86的可移植性而避免使用Intel内在函数? gcc目前在编译使用比目标机器支持的GNU C向量更宽的代码时做得很糟糕。 (你希望它只使用两个128b向量并分别对它们进行操作,但显然它比这更糟糕。)

无论如何, 这个答案显示了如何使用Intel x86内在函数将数据加载到GNU C矢量语法类型中


首先,如果您正在尝试学习编译为优秀代码的内容,那么查看低于-O2编译器输出是浪费时间。 你的main()将优化到-O2的ret

除此之外,通过一次分配一个向量的元素,你会变得很糟糕。


v4df :普通人会调用类型v4df (4 Double Float的向量)或其他东西,而不是vector ,所以当与C ++ std::vector一起使用时,他们不会疯狂。 对于单精度, v8sf 。 IIRC,gcc在__m256d内部使用这样的类型名称。

在x86上,英特尔内部类型(如__m256d )是在GNU C矢量语法之上实现的(这就是为什么你可以在GNU C中执行v1 * v2而不是编写_mm256_mul_pd(v1, v2) )。 您可以自由地从__m256d转换为v4df ,就像我在这里完成的那样。

我已经在function中包含了两种理智的方法,所以我们可以看看他们的asm 。 注意我们是如何从我们在同一个函数中定义的数组加载的,所以编译器不会对它进行优化。

我将它们放在Godbolt编译器资源管理器上,这样你就可以使用各种编译选项和编译器版本来查看asm。

 typedef double v4df __attribute__((vector_size(4 * sizeof(double)))); #include  // 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* intrinsics的args不是编译时常量,编译器将尽其所能制作有效的代码,将所有元素都放入单个向量中 。 通常最好这样做,而不是将存储的C写入tmp数组并从中加载,因为这并不总是最好的策略。 (在通常的存储转发延迟之上,多个窄存储转发到广泛负载的存储转发失败会花费额外的~10个周期(IIRC)的延迟。如果你的double s已经在寄存器中,通常最好只是随机播放他们在一起。)


另请参见如果它们是16字节对齐,是否可以将浮点数直接转换为__m128? 获取用于将单个标量转换为向量的各种内在函数的列表。 x86标签wiki包含英特尔手册及其内在函数查找器的链接。


加载/存储没有Intel内在函数的GNU C向量:

我不确定你是怎么“应该”那样做的。 此问答建议使用一个指向要加载的内存的指针,并使用类型为typedef char __attribute__ ((vector_size (16),aligned (1))) unaligned_byte16; (注意aligned(1)属性)。

你得到*(v4df *)a因为可能是a在32字节边界上没有对齐,但是你使用的是一个假定自然对齐的向量类型。 (就像__m256d如果你取消引用它的指针,而不是使用加载/存储内在函数将对齐信息传递给编译器。)

您可以使用gcc中的等效内在函数来获取x86:__ 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  #include  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] = %f\n", a[0], (*v_ptr)[0], v_copy[0]); printf("a[2] = %f // v[2] = %f // v_copy[0] = %f\n", 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