x86 - 如何使用 avx2 将 24 位 rgb 转换为 32 位?

标签 x86 rgb sse simd avx2

我已经使用 SSSE3 完成了此操作,现在我想知道是否可以使用 AVX2 完成此操作以获得更好的性能?

我用一个零字节填充 24 位 rgb,使用来自 Fast 24-bit array -> 32-bit array conversion? 的代码.

    static const __m128i mask = _mm_setr_epi8(0, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8, -1, 9, 10, 11, -1);
    for (size_t row = 0; row < height; ++row)
    {
        for (size_t column = 0; column < width; column += 16)
        {
            const __m128i *src = reinterpret_cast<const __m128i *>(in + row * in_pitch + column + (column << 1));
            __m128i *dst = reinterpret_cast<__m128i *>(out + row * out_pitch + (column << 2));
            __m128i v[4];
            v[0] = _mm_load_si128(src);
            v[1] = _mm_load_si128(src + 1);
            v[2] = _mm_load_si128(src + 2);
            v[3] = _mm_shuffle_epi8(v[0], mask);
            _mm_store_si128(dst, v[3]);
            v[3] = _mm_shuffle_epi8(_mm_alignr_epi8(v[1], v[0], 12), mask);
            _mm_store_si128(dst + 1, v[3]);
            v[3] = _mm_shuffle_epi8(_mm_alignr_epi8(v[2], v[1], 8), mask);
            _mm_store_si128(dst + 2, v[3]);
            v[3] = _mm_shuffle_epi8(_mm_alignr_epi8(v[2], v[2], 4), mask);
            _mm_store_si128(dst + 3, v[3]);
        }
    }

问题是 _mm256_shuffle_epi8 分别洗牌高 128 位和低 128 位,所以掩码不能替换为

    _mm256_setr_epi8(0, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8, -1, 9, 10, 11, -1, 12, 13, 14, -1, 15, 16, 17, -1, 18, 19, 20, -1, 21, 22, 23, -1);

同时 _mm_alignr_epi8 需要替换为 _mm256_permute2x128_si256_mm256_alignr_epi8

最佳答案

您可以使用 AVX2 相当高效地一次处理 8 个像素(24 个输入字节和 32 个输出字节)。

您只需对齐负载,使您将处理的 24 字节像素 block 居中在 32 字节负载的中间,而不是对齐负载的通常方法到像素 block 2 的开始。这意味着 channel 边界 将落在像素 4 和 5 之间,并且您将在每个 channel 中拥有恰好 4 个像素的字节。结合适当的洗牌掩码,这应该是 SSE 的两倍效率。

例如:

给定一个输入指针 uint8_t input[],您使用非 SIMD 代码处理前四个像素1,然后在 处加载第一个 32 字节input[8] 以便低阶 channel (字节 0-15)在其高阶字节中获取像素 4、5、6、7 的 12 个有效载荷字节,紧接着是接下来的 12 个有效载荷字节高车道中的下一个 4 个像素。然后使用 pshufb 将像素扩展到它们的正确位置(每个 channel 需要一个不同的掩码,因为您将低 channel 中的像素移向较低位置,而高 channel 中的像素移动到更高的职位,但这不会造成问题)。然后下一次加载将在 input[26](24 个字节后),依此类推。

对于完美缓存的输入/输出,您应该使用这种方法在每个周期获得大约 8 个像素的吞吐量 - 限制为 1/周期存储吞吐量和 1/周期随机播放吞吐量。幸运的是,这种方法与始终对齐的存储兼容(因为存储增量是 32 字节)。您将有一些未对齐的负载,但这些负载仍可能以 1/周期发生,因此不应成为瓶颈。

值得注意的是,这种方法在 SIMD 指令集扩展方面“只起作用一次”:当你有 2 条 channel 时它起作用,但不会更多(所以同样的想法不适用于 512 位 AVX512具有 4 个 128 位 channel )。


1这避免了在输入数组之前越界读取:如果您知道这是安全的,则可以避免这一步。

2也就是说,如果您从 addr 加载,则 addr + 16 应该位于像素边界 ((地址 + 16) % 12 == 0), 而不是 addr

关于x86 - 如何使用 avx2 将 24 位 rgb 转换为 32 位?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48731417/

相关文章:

performance - sse 指令是否消耗更多功率/能量?

c++ - 如何正确地从水平阵列垂直读取数据?

assembly - 64 位 x86 中 MOVZX r32、r/m16 和 MOVZX r64、r/m16 的区别

c# - 从 x64 C# 应用程序调用 x86 PowerShell 脚本

c - 函数指针局部变量的意外值

java - 坐标越界 :bufferedimage

c - _m_empty 和 _mm_empty 有什么区别?

c++ - 将浮点值转换为 RGB 值

javascript - 你如何在 Javascript 中获得随机 RGB?

c++ - 我的编译器在做什么? (优化memcpy)