我的原始数据是一堆c数组(长度为1000000的char(无符号)char(8位)。我想将它们加在一起(矢量加法),遵循以下代码中的规则。结果:(无符号)short(16位)的c数组。
我已经阅读了所有SSE和AVX / AVX2,但有一个类似的调用,即多个2个256位寄存器。前4个32位将相乘,每对32位的结果是一个64位,将适合256个寄存器。(_mm256_mul_epi32,_mm256_mul_epu32)
https://www.codeproject.com/Articles/874396/Crunching-Numbers-with-AVX-and-AVX
样例代码:
static inline void adder(uint16_t *canvas, uint8_t *addon, uint64_t count)
{
for (uint64_t i=0; i<count; i++)
canvas[i] += static_cast<uint16_t>(addon[i]);
}
谢谢
添加到@wim答案(这是一个很好的答案)并考虑@Bathsheba的注释,值得相信编译器,还要检查编译器的输出以了解如何执行此操作并检查其在执行您的操作时是否值得d想要。通过Godbolt(适用于msvc,gcc和clang)运行经过稍微修改的代码版本会带来一些不完美的答案。
如果您将自己限制为SSE2,并且低于此答案的假设(以及我测试的内容),则尤其如此。
所有编译器都对代码进行矢量化处理和展开处理,并用于punpcklbw
将uint8_t
s '解压缩为s uint16_t
',然后运行SIMD添加和保存。很好 但是,MSVC倾向于不必要地在内部循环中溢出,而clang仅使用punpcklbw
而不是clang ,punpckhbw
这意味着它将两次加载源数据。GCC正确设置了SIMD零件,但循环约束的开销较高。
因此,从理论上讲,如果您想改进这些版本,则可以使用类似于以下内容的内部函数来滚动自己的版本:
static inline void adder2(uint16_t *canvas, uint8_t *addon, uint64_t count)
{
uint64_t count32 = (count / 32) * 32;
__m128i zero = _mm_set_epi32(0, 0, 0, 0);
uint64_t i = 0;
for (; i < count32; i+= 32)
{
uint8_t* addonAddress = (addon + i);
// Load data 32 bytes at a time and widen the input
// to `uint16_t`'sinto 4 temp xmm reigsters.
__m128i input = _mm_loadu_si128((__m128i*)(addonAddress + 0));
__m128i temp1 = _mm_unpacklo_epi8(input, zero);
__m128i temp2 = _mm_unpackhi_epi8(input, zero);
__m128i input2 = _mm_loadu_si128((__m128i*)(addonAddress + 16));
__m128i temp3 = _mm_unpacklo_epi8(input2, zero);
__m128i temp4 = _mm_unpackhi_epi8(input2, zero);
// Load data we need to update
uint16_t* canvasAddress = (canvas + i);
__m128i canvas1 = _mm_loadu_si128((__m128i*)(canvasAddress + 0));
__m128i canvas2 = _mm_loadu_si128((__m128i*)(canvasAddress + 8));
__m128i canvas3 = _mm_loadu_si128((__m128i*)(canvasAddress + 16));
__m128i canvas4 = _mm_loadu_si128((__m128i*)(canvasAddress + 24));
// Update the values
__m128i output1 = _mm_add_epi16(canvas1, temp1);
__m128i output2 = _mm_add_epi16(canvas2, temp2);
__m128i output3 = _mm_add_epi16(canvas3, temp3);
__m128i output4 = _mm_add_epi16(canvas4, temp4);
// Store the values
_mm_storeu_si128((__m128i*)(canvasAddress + 0), output1);
_mm_storeu_si128((__m128i*)(canvasAddress + 8), output2);
_mm_storeu_si128((__m128i*)(canvasAddress + 16), output3);
_mm_storeu_si128((__m128i*)(canvasAddress + 24), output4);
}
// Mop up
for (; i<count; i++)
canvas[i] += static_cast<uint16_t>(addon[i]);
}
检查输出结果,它绝对比gcc / clang / msvc更好。因此,如果您想获得perf的绝对最后一滴(并具有固定的体系结构),则可能会出现上述情况。但是,这是一个很小的改进,因为编译器已经可以很好地处理此问题,因此我实际上建议不要这样做,而应该信任编译器。
如果您确实认为可以改善编译器,请记住始终进行测试和配置文件以确保您确实是。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句