不管constexpr
, a[pos]
仅作为 GNU C 扩展有效,不能移植到 MSVC。存储到数组,或 C++20std::bit_cast
到一个结构可能会起作用。bit_cast
与 constexpr 兼容,与其他类型双关方法不同。尽管我担心跨编译器编译运行时变量的效率如何pos
bit_cast
确实可以用 clang 编译,并且可以在constexpr
功能。但对于 GCC 编译效率低下。
更正: clang 编译此代码,但如果在以下上下文中调用则拒绝它requires对其进行不断评估。 note: constexpr bit_cast involving type '__attribute__((__vector_size__(4 * sizeof(float)))) float const' (vector of 4 'float' values) is not yet supported
.
在 constexpr 上下文中使用当前 clang 的其他失败尝试:
-
_mm_store_ps
- 不支持。也不是*(__m128*)f = a;
因为它是一个reinterpret_cast。
-
f[0] = vec[0]
初始化器:不,constexpr 中的 clang 甚至不支持 GNU C 本机向量的文字常量索引。
- 联合类型双关:在 constexpr 上下文中读取不允许的非活动成员
-
_mm_cvtss_f32(vec)
- 非 constexpr 函数无法使用,因此没有机会使用if constexpr
用于单独的洗牌和返回。
不起作用的答案,可能在将来的某个时候起作用,但不适用于 clang trunk pre 15.0
#include <cstddef>
#include <immintrin.h>
#include <bit>
// portable, but inefficient with GCC
constexpr float get_data(__m128 a, std::size_t pos) {
struct foo { float f[4]; } s;
s = std::bit_cast<foo>(a);
return s.f[pos];
}
float test_idx2(__m128 a){
return get_data(a, 2);
}
float test_idxvar(__m128 a, size_t pos){
return get_data(a, pos);
}
这些编译为体面的汇编在戈德螺栓上 https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:%271%27,fontScale:14,fontUsePx:%270%27,j:1,lang:c%2B%2B,selection:(endColumn:1,endLineNumber:19,positionColumn:1,positionLineNumber:19,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:%27%23include+%3Ccstddef%3E%0A%23include+%3Cimmintrin.h%3E%0A%23include+%3Cbit%3E%0A%0Aconstexpr+float+get_data(__m128+a,+std::size_t+pos)+%7B%0A++++struct+foo+%7B+float+f%5B4%5D%3B+%7D+s%3B%0A++++s+%3D+std::bit_cast%3Cfoo%3E(a)%3B%0A++++return+s.f%5Bpos%5D%3B%0A++//return+a%5Bpos%5D%3B%0A%7D%0A%0Afloat+test_idx2(__m128+a)%7B%0A++++return+get_data(a,+2)%3B%0A%7D%0A%0Afloat+test_idxvar(__m128+a,+size_t+pos)%7B%0A++++return+get_data(a,+pos)%3B%0A%7D%0A%27),l:%275%27,n:%270%27,o:%27C%2B%2B+source+%231%27,t:%270%27)),k:46.715328467153284,l:%274%27,n:%270%27,o:%27%27,s:0,t:%270%27),(g:!((g:!((h:compiler,i:(compiler:clang1400,filters:(b:%270%27,binary:%271%27,commentOnly:%270%27,demangle:%270%27,directives:%270%27,execute:%271%27,intel:%270%27,libraryCode:%270%27,trim:%271%27),flagsViewOpen:%271%27,fontScale:14,fontUsePx:%270%27,j:1,lang:c%2B%2B,libs:!(),options:%27-O3+-march%3Dhaswell+-std%3Dgnu%2B%2B20%27,selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1,tree:%271%27),l:%275%27,n:%270%27,o:%27x86-64+clang+14.0.0+(C%2B%2B,+Editor+%231,+Compiler+%231)%27,t:%270%27)),k:45.88257813684033,l:%274%27,m:50,n:%270%27,o:%27%27,s:0,t:%270%27),(g:!((h:output,i:(compilerName:%27x86-64+clang+14.0.0%27,editorid:1,fontScale:14,fontUsePx:%270%27,j:1,wrap:%271%27),l:%275%27,n:%270%27,o:%27Output+of+x86-64+clang+14.0.0+(Compiler+%231)%27,t:%270%27)),header:(),l:%274%27,m:50,n:%270%27,o:%27%27,s:0,t:%270%27)),k:53.284671532846716,l:%273%27,n:%270%27,o:%27%27,t:%270%27)),l:%272%27,n:%270%27,o:%27%27,t:%270%27)),version:4,与您从 clang 中得到的结果相同a[pos]
。我用了-O3 -march=haswell -std=gnu++20
# clang 14 -O3 -march=haswell -std=gnu++20
# get_data has no asm output; constexpr is like inline in that respect
test_idx2(float __vector(4)):
vpermilpd xmm0, xmm0, 1 # xmm0 = xmm0[1,0]
ret
test_idxvar(float __vector(4), unsigned long):
vmovups xmmword ptr [rsp - 16], xmm0
vmovss xmm0, dword ptr [rsp + 4*rdi - 16] # xmm0 = mem[0],zero,zero,zero
ret
对于运行时变量索引来说,存储/重新加载是一个明智的策略,尽管vmovd
/ vpermilps
由于 AVX 引入了使用双字索引的可变控制洗牌,因此这将是一个选项。超出范围的索引是 UB,因此编译器在这种情况下不需要返回任何特定数据。
Using vpermilpd
对于常数索引2
与代码大小相比是一种浪费vmovhlps xmm0, xmm0, xmm0
or vunpckhpd
。它需要更长的 VEX 前缀和立即数(即 2 个字节的机器代码大小),但在大多数 CPU 上的其他性能相同。
不幸的是 GCC 做得不太好
即使对于固定索引,我们也会得到存储/重新加载2
,甚至更糟糕的是,通过 GP 整数寄存器弹跳来重新加载。这是一个错过的优化,但我不知道如果报告它会多快得到修复。所以如果你打算这样做,也许#ifdef __clang__
or #ifdef __llvm__
对于位广播,以及#ifdef __GNUC__
for a[pos]
。 (Clang 定义__GNUC__
所以在特殊外壳叮当声之后检查一下。)
# gcc12 -O3 -march=haswell -std=gnu++20
test_idx2(float __vector(4)):
vmovaps XMMWORD PTR [rsp-24], xmm0
mov rax, QWORD PTR [rsp-16]
vmovd xmm0, eax # slow: should have loaded directly from mem
ret
test_idxvar(float __vector(4), unsigned long):
vmovdqa XMMWORD PTR [rsp-24], xmm0
vmovss xmm0, DWORD PTR [rsp-24+rdi*4] # this is fine, same as clang
ret
有趣的是,运行时变量版本没有针对 GCC 的相同反优化。