将大型char8的c数组转换为short16的最快方法是什么?

Ngọ*_*yễn 2 c c++ intel intrinsics

我的原始数据是一堆c数组(长度为1000000)(无符号)char(8位)。我想将它们加在一起(矢量加法),遵循以下代码中的规则。结果:(无符号)short(16位)的c数组。

我已经阅读了所有SSE和AVX / AVX2,但有一个类似的调用,即多个2个256位寄存器。前4个32位将相乘,每对32位的结果是一个64位,将适合256个寄存器。(_mm256_mul_epi32,_mm256_mul_epu32)

Firgure

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]);
}
Run Code Online (Sandbox Code Playgroud)

谢谢

Mik*_*ine 5

添加到@wim答案(这是一个很好的答案)并考虑@Bathsheba注释,值得相信编译器,同时还要检查编译器输出的内容,以学习如何执行此操作并检查其在执行您的操作时是否值得d想要。通过Godbolt(适用于msvc,gcc和clang)运行经过稍微修改的代码版本会带来一些不完美的答案。

如果您将自己限制为SSE2,并且低于此答案的假设条件(以及我测试的条件),则尤其如此。

所有编译器都对代码进行矢量化处理和展开处理,并用于punpcklbwuint8_ts '解压缩为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]);
}
Run Code Online (Sandbox Code Playgroud)

检查输出结果,它绝对比gcc / clang / msvc更好。因此,如果您想获得绝对的性能下降(并且具有固定的体系结构),则可能会出现上述情况。但是,这是一个非常小的改进,因为编译器已经可以完美地处理此问题,因此我实际上建议不要这样做,而应该信任编译器。

如果您确实认为可以改善编译器,请记住始终进行测试和配置文件以确保您确实是。