我有来自 uint8_t
值的灰度图像。我想将数据加载到 SIMD。我加载 16 个值并将它们转换为两个 __m256
浮点寄存器。
我使用:
uint8_t * data = .....
size_t index = ....
//load 16 uint8_t (16 * 8 = 128bit)
__m128i val = _mm_loadu_si128((const __m128i*)(data + index));
//convert to 16bit int
__m128i lo16 = _mm_unpacklo_epi8(val, _mm_setzero_si128());
__m128i hi16 = _mm_unpackhi_epi8(val, _mm_setzero_si128());
//convert to 32bit int
__m256i lo32 = _mm256_cvtepi16_epi32(lo16);
__m256i hi32 = _mm256_cvtepi16_epi32(hi16);
//convert to float
__m256 lo = _mm256_cvtepi32_ps(lo32);
__m256 hi = _mm256_cvtepi32_ps(hi32);
是否有比我的解决方案更好的方法(无需 AVX-512)?
加载_mm256_loadu_si256
并将其“拆分”到4个__m256
寄存器会更好吗?
最佳答案
打开 _mm256_loadu_si256
的高车道会花费更多的洗牌成本。 。大多数 AVX2 CPU 的负载吞吐量高于随机播放吞吐量,并且每个输出向量至少需要 1 次随机播放,因此您已经可以对 2 epi32
执行 1 次加载和 4 次随机播放。向量是一个糟糕的权衡。
如果有什么的话,最好使用 2x _mm256_cvtepu8_epi32
获取 cvtepi32_ps 的两个输入向量,每次洗牌一次加载。
使用内存源有点痛苦pmovz/sx
因为你需要告诉编译器你正在对 __m128i
进行窄加载。 (为了安全),并且某些编译器不会优化 vpmovzx
到内存源中的零扩展加载。 参见Loading 8 chars from memory into an __m256 variable as packed single precision floats
但显然,自从我最初在 2015 年写下这个答案以来,情况已经有所改善; GCC9 及更高版本修复了错过优化的错误,现在折叠了 _mm_loadl_epi64( (const __m128i*)p)
进入 vpmovzx
的内存源。 clang 和 ICC 都很好,即使是旧版本。 MSVC 仍然使用单独的 vmovq
进行糟糕的代码生成。 ,即使有 -march:AVX2
,甚至 v19.28 和“最新”。 (Godbolt)。
在 Intel CPU 上 vpmovzxbd ymm, qword [mem]
始终为 2 uop;无法对负载进行微熔断(仅适用于 xmm 目标)https://uops.info/table.html ,因此即使编译器确实设法将 64 位内存源折叠到 mem 操作数而不是使用 vmovq
,您也不会获得任何东西(除了代码大小)。负载。
但在 Zen2 上,该指令的吞吐量为 2/时钟,而 vpmovzxbd ymm, xmm
的吞吐量低于 1/时钟吞吐量。 (wd
随机播放或您正在使用的符号扩展版本相同,vpmovsxwd = epi16_epi32)。因此,如果您关心 Zen CPU,尤其是 Zen 2,您确实希望编译器能够正确执行此操作。
关于SIMD (AVX2) - 将 uint8_t 值加载到多个 float __m256 寄存器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66705841/