您的标量版本看起来很糟糕(使用类型punning的引用强制转换),并且可能编译为效率非常低的ASM,这比将每个32位元素复制到位模式慢得多。
1.0f
. 这只需要一个整数和一个或来做标量(如果MSVC不能为您自动向量化),但是如果编译器将它复制到一个xmm寄存器或其他地方,我不会感到惊讶。
你的第一个手动矢量化版本甚至没有做同样的工作,不过,它只是屏蔽了所有要离开的非符号位。
-0.0f
或
+0.0f
. 所以它会编译成一个
vandps ymm0, ymm7, [rdi]
和一个模拟人生商店
vmovups [rdi], ymm0
,加上一些循环开销。
不是那个加法
_mm256_or_ps
具有
set1(1.0f)
会减慢任何速度,您仍然会在缓存带宽或每时钟一次的存储吞吐量上遇到瓶颈。
然后你把它编辑成一个版本
-1.0f .. +1.0f
范围,使幅度小于1.0的输入保持不变。这不会比两个位操作慢,除了haswell(魔鬼峡谷)只在端口5运行fp布尔值,而在端口0或端口1运行实际fp值。
尤其是如果你没有用你的花车做任何其他的事情,你实际上会想用
_si256
内部函数只使用avx2整数指令,以提高haswell的速度。(但是如果没有avx2,代码就无法运行。)
在Skylake和更新版本上,FP布尔值可以使用所有3个矢量ALU端口。(
https://agner.org/optimize/
用于说明表和UARCH指南。)
您的代码应该如下所示:
// outside the loop if you want
const __m256i ones = _mm256_castps_si256(_mm256_set1_ps(1.0f));
for (something ; p += whatever) {
__m256i floats = _mm256_load_si256( (const __m256i*)p );
__m256i signs = _mm256_and_si256(floats, _mm256_set1_epi32(0x80000000));
__m256i applied = _mm256_or_si256(signs, ones);
_mm256_store_si256((__m256i*)p, applied);
}