为什么 gcc -O3 处理 avx256 compare intrinsic 的方式与 gcc -O0 和 clang 不同?
Why does gcc -O3 handle avx256 compare intrinsic differently than gcc -O0 and clang?
我想设置两个整数向量并将它们与 SIMD 进行比较,然后使用此掩码对压缩浮点数进行混合操作。我生成了以下代码:
#include <immintrin.h>
#include <stdio.h>
#include <string.h>
int main(){
__m256i is = _mm256_setr_epi32(1, 2, 3, 4, 5, 6, 7, 8);
__m256i js = _mm256_set1_epi32(1); // integer bit-patterns
__m256 mask = _mm256_cmp_ps(is,js, _CMP_EQ_OQ); // compare as subnormal floats
float val[8];
memcpy(val, &mask, sizeof(val));
printf("%f %f %f %f %f %f %f %f \n", val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7]);
}
适用于 gcc -mavx main.c
以及 clang -mavx main.c
和 clang -O3 -mavx main.c
。
(编者注:当 cmpps 将那些非规范输入视为 0.0
时,它将与 -ffast-math
中断,因此所有比较都是真实的。您希望 AVX2 _mm256_cmp_epi32
执行 integer 比较,然后 _mm256_castsi256_ps
结果。但这与关于 gcc -O0
和 clang 允许从 __m256i
到 __m256
的隐式转换的问题无关)
但是,当我使用 gcc -O3 -mavx main.c
时,我收到以下错误消息:
main.c: In function ‘main’:
main.c:9:33: error: incompatible type for argument 1 of ‘_mm256_cmp_ps’
9 | __m256 mask = _mm256_cmp_ps(is,js, _CMP_EQ_OQ);
| ^~
| |
| __m256i {aka __vector(4) long long int}
In file included from /usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/include/immintrin.h:51,
from main.c:1:
/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/include/avxintrin.h:404:23: note: expected ‘__m256’ {aka ‘__vector(8) float’} but argument is of type ‘__m256i’ {aka ‘__vector(4) long long int’}
404 | _mm256_cmp_ps (__m256 __X, __m256 __Y, const int __P)
| ~~~~~~~^~~
main.c:9:36: error: incompatible type for argument 2 of ‘_mm256_cmp_ps’
9 | __m256 mask = _mm256_cmp_ps(is,js, _CMP_EQ_OQ);
| ^~
| |
| __m256i {aka __vector(4) long long int}
In file included from /usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/include/immintrin.h:51,
from main.c:1:
/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/include/avxintrin.h:404:35: note: expected ‘__m256’ {aka ‘__vector(8) float’} but argument is of type ‘__m256i’ {aka ‘__vector(4) long long int’}
404 | _mm256_cmp_ps (__m256 __X, __m256 __Y, const int __P)
| ~~~~~~~^~~
我注意到两件事。首先,编译器似乎将 is
视为 __m256i {aka __vector(4) long long int}
而它包含 8 个整数。其次,编译器的抱怨是正确的,因为英特尔内在函数指南 1 将参数显示为 __m256
。我现在很困惑为什么这段代码在一开始就起作用了。如果它确实是正确的,因为整数被转换为浮点数,那么我不明白为什么它不适用于 gcc -O3
。
我不想使用 _mm256_cmpeq_epi32
其中 returns 和 __m256i
并且(似乎没有)没有接受这种掩码的 blend_ps
指令。
为什么编译器的行为不同,执行此操作的正确方法是什么?
编译器版本
$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: /build/gcc/src/gcc/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-pkgversion='Arch Linux 9.3.0-1' --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++,d --enable-shared --enable-threads=posix --with-system-zlib --with-isl --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-lto --enable-plugin --enable-install-libiberty --with-linker-hash-style=gnu --enable-gnu-indirect-function --enable-multilib --disable-werror --enable-checking=release --enable-default-pie --enable-default-ssp --enable-cet=auto gdc_include_dir=/usr/include/dlang/gdc
Thread model: posix
gcc version 9.3.0 (Arch Linux 9.3.0-1)
$ clang -v
clang version 10.0.0
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-pc-linux-gnu/8.4.0
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-pc-linux-gnu/9.3.0
Found candidate GCC installation: /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/8.4.0
Found candidate GCC installation: /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/9.3.0
Found candidate GCC installation: /usr/lib/gcc/x86_64-pc-linux-gnu/8.4.0
Found candidate GCC installation: /usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0
Found candidate GCC installation: /usr/lib64/gcc/x86_64-pc-linux-gnu/8.4.0
Found candidate GCC installation: /usr/lib64/gcc/x86_64-pc-linux-gnu/9.3.0
Selected GCC installation: /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/9.3.0
Candidate multilib: .;@m64
Candidate multilib: 32;@m32
Selected multilib: .;@m64
Found CUDA installation: /opt/cuda, version 10.1
[1] https://software.intel.com/sites/landingpage/IntrinsicsGuide/
First of all, the compiler seems to treat is
as __m256i {aka __vector(4) long long int}
whereas it contains 8 ints.
__m128i
和更大的相似向量没有指定存储在其中的整数的实际大小(和数量)。您可以使用相同的 __m128i
类型来存储 16 uint8_t
或 2 uint64_t
或介于两者之间的任何内容。重要的部分是它用于存储整数。它是对 __m128i
和更大的相似向量的操作,它指定将向量解释为一组给定宽度的整数。例如,_mm_add_epi16
和 _mm_add_epi32
都接受 __m128i
参数,但第一个将其解释为 8 uint16_t
的向量,第二个 - 4 uint32_t
s.
Secondly, the compiler is correct to complain, because the intel intrinsics guide 1 shows the arguments as __m256
.
我认为,编译器的抱怨是正确的。它用 -O0
编译代码似乎是一个编译器错误。在 gcc 中,__m128i
和其他向量是使用 __attribute__((vector_size))
属性实现的,文档说应该使用 __builtin_convertvector
intrinsic 在不同类型的向量之间进行转换。
英特尔软件开发人员手册第 3.1.1.10 节中 __m128i
和其他向量类型的原始定义没有明确说明不同类型向量的可转换性,尽管它确实是这样说的:
These SIMD data types are not basic Standard C data types or C++ objects, so they may be used only with the assignment operator, passed as function arguments, and returned from a function call.
鉴于此,我认为这些向量类型不应该是可隐式转换的。你当然不能依赖转换,如果它确实编译,会有任何特定的行为。特别是考虑到整数向量不指定其元素的大小。因此,您应该始终使用内在函数来定义所需的转换类型,例如_mm_cvtepi32_ps
/_mm_cvtepi32_pd
或 _mm_castsi128_ps
/_mm_castsi128_pd
.
I did not want to use _mm256_cmpeq_epi32
which returns an __m256i
and there (seems to be no) is no blend_ps
instruction that accepts such a mask.
_mm256_cmpeq_epi32
是AVX2,AVX2里面有_mm256_blendv_epi8
。如果您仅限于 AVX,那么您必须对 128 位整数向量进行操作。
使用_mm256_cmp_ps
对整数向量进行运算是不正确的,因为它的行为不同于整数比较。特别是,如果至少一个输入操作数与 NaN 位模式匹配(例如,使用 _CMP_EQ_OQ
操作数,您的比较将始终在结果向量元素中 return 0
)。
我想设置两个整数向量并将它们与 SIMD 进行比较,然后使用此掩码对压缩浮点数进行混合操作。我生成了以下代码:
#include <immintrin.h>
#include <stdio.h>
#include <string.h>
int main(){
__m256i is = _mm256_setr_epi32(1, 2, 3, 4, 5, 6, 7, 8);
__m256i js = _mm256_set1_epi32(1); // integer bit-patterns
__m256 mask = _mm256_cmp_ps(is,js, _CMP_EQ_OQ); // compare as subnormal floats
float val[8];
memcpy(val, &mask, sizeof(val));
printf("%f %f %f %f %f %f %f %f \n", val[0], val[1], val[2], val[3], val[4], val[5], val[6], val[7]);
}
适用于 gcc -mavx main.c
以及 clang -mavx main.c
和 clang -O3 -mavx main.c
。
(编者注:当 cmpps 将那些非规范输入视为 0.0
时,它将与 -ffast-math
中断,因此所有比较都是真实的。您希望 AVX2 _mm256_cmp_epi32
执行 integer 比较,然后 _mm256_castsi256_ps
结果。但这与关于 gcc -O0
和 clang 允许从 __m256i
到 __m256
的隐式转换的问题无关)
但是,当我使用 gcc -O3 -mavx main.c
时,我收到以下错误消息:
main.c: In function ‘main’:
main.c:9:33: error: incompatible type for argument 1 of ‘_mm256_cmp_ps’
9 | __m256 mask = _mm256_cmp_ps(is,js, _CMP_EQ_OQ);
| ^~
| |
| __m256i {aka __vector(4) long long int}
In file included from /usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/include/immintrin.h:51,
from main.c:1:
/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/include/avxintrin.h:404:23: note: expected ‘__m256’ {aka ‘__vector(8) float’} but argument is of type ‘__m256i’ {aka ‘__vector(4) long long int’}
404 | _mm256_cmp_ps (__m256 __X, __m256 __Y, const int __P)
| ~~~~~~~^~~
main.c:9:36: error: incompatible type for argument 2 of ‘_mm256_cmp_ps’
9 | __m256 mask = _mm256_cmp_ps(is,js, _CMP_EQ_OQ);
| ^~
| |
| __m256i {aka __vector(4) long long int}
In file included from /usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/include/immintrin.h:51,
from main.c:1:
/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/include/avxintrin.h:404:35: note: expected ‘__m256’ {aka ‘__vector(8) float’} but argument is of type ‘__m256i’ {aka ‘__vector(4) long long int’}
404 | _mm256_cmp_ps (__m256 __X, __m256 __Y, const int __P)
| ~~~~~~~^~~
我注意到两件事。首先,编译器似乎将 is
视为 __m256i {aka __vector(4) long long int}
而它包含 8 个整数。其次,编译器的抱怨是正确的,因为英特尔内在函数指南 1 将参数显示为 __m256
。我现在很困惑为什么这段代码在一开始就起作用了。如果它确实是正确的,因为整数被转换为浮点数,那么我不明白为什么它不适用于 gcc -O3
。
我不想使用 _mm256_cmpeq_epi32
其中 returns 和 __m256i
并且(似乎没有)没有接受这种掩码的 blend_ps
指令。
为什么编译器的行为不同,执行此操作的正确方法是什么?
编译器版本
$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: /build/gcc/src/gcc/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-pkgversion='Arch Linux 9.3.0-1' --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++,d --enable-shared --enable-threads=posix --with-system-zlib --with-isl --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-lto --enable-plugin --enable-install-libiberty --with-linker-hash-style=gnu --enable-gnu-indirect-function --enable-multilib --disable-werror --enable-checking=release --enable-default-pie --enable-default-ssp --enable-cet=auto gdc_include_dir=/usr/include/dlang/gdc
Thread model: posix
gcc version 9.3.0 (Arch Linux 9.3.0-1)
$ clang -v
clang version 10.0.0
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-pc-linux-gnu/8.4.0
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-pc-linux-gnu/9.3.0
Found candidate GCC installation: /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/8.4.0
Found candidate GCC installation: /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/9.3.0
Found candidate GCC installation: /usr/lib/gcc/x86_64-pc-linux-gnu/8.4.0
Found candidate GCC installation: /usr/lib/gcc/x86_64-pc-linux-gnu/9.3.0
Found candidate GCC installation: /usr/lib64/gcc/x86_64-pc-linux-gnu/8.4.0
Found candidate GCC installation: /usr/lib64/gcc/x86_64-pc-linux-gnu/9.3.0
Selected GCC installation: /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/9.3.0
Candidate multilib: .;@m64
Candidate multilib: 32;@m32
Selected multilib: .;@m64
Found CUDA installation: /opt/cuda, version 10.1
[1] https://software.intel.com/sites/landingpage/IntrinsicsGuide/
First of all, the compiler seems to treat
is
as__m256i {aka __vector(4) long long int}
whereas it contains 8 ints.
__m128i
和更大的相似向量没有指定存储在其中的整数的实际大小(和数量)。您可以使用相同的 __m128i
类型来存储 16 uint8_t
或 2 uint64_t
或介于两者之间的任何内容。重要的部分是它用于存储整数。它是对 __m128i
和更大的相似向量的操作,它指定将向量解释为一组给定宽度的整数。例如,_mm_add_epi16
和 _mm_add_epi32
都接受 __m128i
参数,但第一个将其解释为 8 uint16_t
的向量,第二个 - 4 uint32_t
s.
Secondly, the compiler is correct to complain, because the intel intrinsics guide 1 shows the arguments as
__m256
.
我认为,编译器的抱怨是正确的。它用 -O0
编译代码似乎是一个编译器错误。在 gcc 中,__m128i
和其他向量是使用 __attribute__((vector_size))
属性实现的,文档说应该使用 __builtin_convertvector
intrinsic 在不同类型的向量之间进行转换。
英特尔软件开发人员手册第 3.1.1.10 节中 __m128i
和其他向量类型的原始定义没有明确说明不同类型向量的可转换性,尽管它确实是这样说的:
These SIMD data types are not basic Standard C data types or C++ objects, so they may be used only with the assignment operator, passed as function arguments, and returned from a function call.
鉴于此,我认为这些向量类型不应该是可隐式转换的。你当然不能依赖转换,如果它确实编译,会有任何特定的行为。特别是考虑到整数向量不指定其元素的大小。因此,您应该始终使用内在函数来定义所需的转换类型,例如_mm_cvtepi32_ps
/_mm_cvtepi32_pd
或 _mm_castsi128_ps
/_mm_castsi128_pd
.
I did not want to use
_mm256_cmpeq_epi32
which returns an__m256i
and there (seems to be no) is noblend_ps
instruction that accepts such a mask.
_mm256_cmpeq_epi32
是AVX2,AVX2里面有_mm256_blendv_epi8
。如果您仅限于 AVX,那么您必须对 128 位整数向量进行操作。
使用_mm256_cmp_ps
对整数向量进行运算是不正确的,因为它的行为不同于整数比较。特别是,如果至少一个输入操作数与 NaN 位模式匹配(例如,使用 _CMP_EQ_OQ
操作数,您的比较将始终在结果向量元素中 return 0
)。