这是平台特定的问题。速度至关重要。
将一个字节解包为 8 个单精度浮点数的数组以便零映射到零而一个映射到 1 的最快方法是什么?
我最终使用 8 位掩码和 7 位移位来解包成 8 个 int32,然后使用 AVX 指令将 int32 转换为浮点数。
我的平台是在支持 AVX(但没有 AVX2)的 CPU 上运行的 Windows 64 位。编译器:Visual Studio 2013。
谢谢。
最佳答案
循环、条件和遍历内存中的实际数组当然不是 vector 方式。所以这是另一个想法,尽管它只在 AVX 中有点烦人。由于没有 AVX2,您几乎无法使用 ymm 寄存器做任何事情(无论如何都没有用),只需使用两个 xmm 寄存器,然后最后 vinsertf128
高位部分形成整个事情。只要 xmm 寄存器上的操作使用 VEX 编码指令(所以 'v' 放在所有内容的前面,即使它看起来没有必要),这样混合就可以了。
无论如何,这个想法是在每个双字中放置一个字节的拷贝,并且每个 channel 都有正确的位并比较以形成掩码。最后,我们可以执行单个按位 AND 将掩码转换为 0f 或 1f。
因此,首先在各处获取该字节,假设它在 eax
中,这并不重要:
vmovd xmm0, eax
vpshufd xmm0, xmm0, 0
提取正确的位:
vpand xmm0, xmm0, [low_mask]
vpand xmm1, xmm0, [high_mask]
掩码是
1, 2, 4, 8
和 16, 32, 64, 128
(这是按内存顺序排列的,如果您使用 _mm_set_epi32
它们必须相反)比较形成面具:
vpxor xmm2, xmm2, xmm2
vpcmpgtd xmm0, xmm0, xmm2
vpcmpgtd xmm1, xmm1, xmm2
合并:
vinsertf128 ymm0, ymm0, xmm1, 1
变成0f或1f:
vandps ymm0, ymm0, [ones]
ones
只是 1f 重复了 8 次。我不知道这是否更快,但值得一试。此外,这些都没有经过测试。
我试图将它转换为内在函数,但我不知道我在做什么(并且没有经过测试)。另外,请注意它使用 VEX 前缀编译,否则会导致昂贵的模式切换。
// broadcast
__m128i low = _mm_set1_epi32(mask);
__m128i high = _mm_set1_epi32(mask);
// extract bits
low = _mm_and_si128(low, _mm_set_epi32(8, 4, 2, 1));
high = _mm_and_si128(high, _mm_set_epi32(128, 64, 32, 16));
// form masks
low = _mm_cmpgt_epi32(low, _mm_setzero_si128());
high = _mm_cmpgt_epi32(high, _mm_setzero_si128());
// stupid no-op casts
__m256 low2 = _mm256_castps128_ps256(_mm_castsi128_ps(low));
__m128 high2 = _mm_castsi128_ps(high);
// merge
__m256 total = _mm256_insertf128_ps(low2, high2, 1);
// convert to 0f or 1f
total = _mm256_and_ps(total, _mm256_set1_ps(1.0f));
至少使用 GCC,可以生成 OK 代码。它使用
vbroadcastss
作为 set1
(而不是我使用的 vpshufd
),我不确定这个想法有多好(这意味着它必须通过内存反弹那个 int )。使用 AVX2,它可以简单得多:
__m256i x = _mm256_set1_epi32(mask);
x = _mm256_and_si256(x, _mm256_set_epi32(128, 64, 32, 16, 8, 4, 2, 1));
x = _mm256_cmpgt_epi32(x, _mm256_setzero_si256());
x = _mm256_and_si256(x, _mm256_set1_epi32(0x3F800000));
return _mm256_castsi256_ps(x);
关于c++ - 将位解包为单精度浮点数的最快方法,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/30006584/