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

X86 如何使用avx2将24位rgb转换为32位?,x86,rgb,sse,simd,avx2,X86,Rgb,Sse,Simd,Avx2,我已经用SSSE3实现了这一点,现在我想知道是否可以用AVX2实现这一点以获得更好的性能 我使用来自的代码将24位rgb填充为一个零字节 此外,\u mm256\u permute2x128\u si256和\u mm256\u aligner\u epi8使用AVX2,您可以合理高效地一次处理8个像素(24个输入字节和32个输出字节) 你只需要对齐你的负载,使得你要处理的24字节的像素集中在32字节负载的中间,而不是将负载对齐到Pixels2块的开始的通常方法。这意味着车道边界将位于像素4和5

我已经用SSSE3实现了这一点,现在我想知道是否可以用AVX2实现这一点以获得更好的性能

我使用来自的代码将24位rgb填充为一个零字节


此外,
\u mm256\u permute2x128\u si256
\u mm256\u aligner\u epi8
使用AVX2,您可以合理高效地一次处理8个像素(24个输入字节和32个输出字节)

<>你只需要对齐你的负载,使得你要处理的24字节的像素集中在32字节负载的中间,而不是将负载对齐到Pixels2块的开始的通常方法。这意味着车道边界将位于像素4和5之间,每个车道中正好有4个像素的字节。结合适当的洗牌掩码,这应该是SSE效率的两倍

例如:

给定一个输入指针
uint8\u t input[]
使用非SIMD code1处理前四个像素,然后在
input[8]
处加载第一个32字节,以便低阶通道(字节0-15)在其高阶字节中获得像素4、5、6、7的12个有效负载字节,紧跟其后的是高速通道中接下来4个像素的接下来12个有效负载字节。然后使用
pshufb
将像素扩展到正确的位置(每个车道需要不同的遮罩,因为将低车道中的像素移到较低的位置,将高车道中的像素移到较高的位置,但这不会造成问题)。然后下一次加载将在
输入[26]
(24字节后)处进行,依此类推

使用这种方法,每个周期的吞吐量应该为8像素左右,因为完全缓存的输入/输出限制在1/周期存储吞吐量和1/周期洗牌吞吐量上。幸运的是,这种方法与始终对齐的存储兼容(因为存储增量是32字节)。您将有一些未对齐的负载,但这些负载仍可能发生在1/周期,因此不应成为瓶颈

值得注意的是,就SIMD指令集扩展而言,这种方法“只工作一次”:当您有2条通道时,它工作,但不能更多(因此相同的想法不适用于具有4条128位通道的512位AVX512)


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


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

)的是
addr+16
,这里是原始的SSSE3代码,并加入了一些我自己的调度

void DspConvertPcm(f32* pOutBuffer, const s24* pInBuffer, size_t totalSampleCount)
{
    constexpr f32 fScale = static_cast<f32>(1.0 / (1<<23));

    size_t i = 0;
    size_t vecSampleCount = 0;

#if defined(SFTL_SSE2)
    if (CpuInfo::GetSupports_SIMD_I32x8())
    {
        vecSampleCount = DspConvertPcm_AVX2(pOutBuffer, pInBuffer, totalSampleCount);
    }
    else
    if (CpuInfo::GetSupports_SSE3())
    {
        const auto vScale = _mm_set1_ps(fScale);
        const auto mask = _mm_setr_epi8(-1, 0, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8, -1, 9, 10, 11);

        constexpr size_t step = 16;
        vecSampleCount = (totalSampleCount / step) * step;

        for (; i < vecSampleCount; i += step)
        {
            const auto* pSrc = reinterpret_cast<const __m128i*>(pInBuffer + i);
            auto* pDst = pOutBuffer + i;

            const auto sa = _mm_loadu_si128(pSrc + 0);
            const auto sb = _mm_loadu_si128(pSrc + 1);
            const auto sc = _mm_loadu_si128(pSrc + 2);

            const auto da = _mm_srai_epi32(_mm_shuffle_epi8(sa, mask), 8);
            const auto db = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sb, sa, 12), mask), 8);
            const auto dc = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sc, sb,  8), mask), 8);
            const auto dd = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sc, sc,  4), mask), 8);

            //  Convert to float and store
            _mm_storeu_ps(pDst + 0,  _mm_mul_ps(_mm_cvtepi32_ps(da), vScale));
            _mm_storeu_ps(pDst + 4,  _mm_mul_ps(_mm_cvtepi32_ps(db), vScale));
            _mm_storeu_ps(pDst + 8,  _mm_mul_ps(_mm_cvtepi32_ps(dc), vScale));
            _mm_storeu_ps(pDst + 12, _mm_mul_ps(_mm_cvtepi32_ps(dd), vScale));
        }
    }
#endif

    for (; i < totalSampleCount; i += 1)
    {
        pOutBuffer[i] = (static_cast<s32>(pInBuffer[i])) * fScale;
    }
}
void DspConvertPcm(f32*pOutBuffer,常数s24*pInBuffer,大小\u t totalSampleCount)
{

constexpr f32 fScale=静态_cast(1.0/(1)你有尝试过吗?如果有,请将你的代码发布到目前为止。如果没有,也许你可以发布你现有的SSE代码作为起点?24位到32位到底是什么意思?添加alpha组件?将每个通道的8位扩展到11,10,11或10,12,10?谢谢大家的评论,我编辑了这个问题以添加细节。请让我知道,如果它还不够清楚的话,Axx2的内道性质意味着SSSE3<代码> pSUFB<<代码>可能仍然是最好的选择。但是,你应该考虑做不对齐的负载,而不是使用<代码>我们的代码对每个存储进行多次洗牌。@Mysticial-我的测试没有显示重叠写入的惩罚。当然,可能会有未对齐写入与缓存线重叠的惩罚,但对与先前写入重叠的写入没有特别的惩罚。这意味着您要合并一组具有e奇数大小,一系列重叠写入是一种很好的策略,每个周期运行1段(如果每个段适合寄存器,加上不可避免的缓存线交叉的一些惩罚)。谢谢你的回答。但是,虽然最终的输出确实给了我们正确的结果,但我实际上试了一下,它比OP引用的SSE版本慢了大约50%。我猜这是由于它不可避免地会进行大多数未对齐的加载,并且它实际上会丢弃25%的加载数据ata.我不推荐这种方法。FWIW,我实际上能够通过使用相同的SSE3代码并简单地启用启用启用vex编码的AVX编译器标志来获得大约40%的性能提升。似乎这将给我们带来最好的胜利,除非其他人拥有SSE3版本的真正AVX2端口。我们可以将AVX的车道边界限制归咎于英特尔Actions.@Kumputer-什么类型的机器,你可以共享你的代码和基准测试?更多细节请参见下面的答案。i7-6700HQ是Skylake,而不是Haswell。无论如何,你的符号扩展是24位到32位。这与RGB到RGBA或RGB0是不同的问题,在RGB或RGB0中,你想用固定值填充a字节(在本例中为0)。此代码看起来很有用,但发布的问题不正确。虽然我怀疑@BeeOnRope的未对齐加载方法,因此每个
srai
只需要一次随机加载会更好,特别是在Haswell/Skylake上(1/时钟随机加载吞吐量,低于IvyBridge上的2,但仍然是2/时钟加载吞吐量)。您已经在AVX2版本上这样做了,看起来像,但不是SSSE3,所以在Haswell/Skylake Pentium/Celeron CPU上速度会慢一些,因为只有SSE4.2。我还没有尝试找出在SnB或Nehalem上哪一个更快。实际的Core 2未对齐加载速度慢(即使不跨越缓存线边界)可能会受益于
palignr
,至少Penryn,可能也会受益于Conroe,即使Conroe的洗牌速度很慢。顺便说一句,您可以使用重叠向量代替标量清理,除非缓冲区大小可能小于一个展开的内部循环。
    _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);
void DspConvertPcm(f32* pOutBuffer, const s24* pInBuffer, size_t totalSampleCount)
{
    constexpr f32 fScale = static_cast<f32>(1.0 / (1<<23));

    size_t i = 0;
    size_t vecSampleCount = 0;

#if defined(SFTL_SSE2)
    if (CpuInfo::GetSupports_SIMD_I32x8())
    {
        vecSampleCount = DspConvertPcm_AVX2(pOutBuffer, pInBuffer, totalSampleCount);
    }
    else
    if (CpuInfo::GetSupports_SSE3())
    {
        const auto vScale = _mm_set1_ps(fScale);
        const auto mask = _mm_setr_epi8(-1, 0, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8, -1, 9, 10, 11);

        constexpr size_t step = 16;
        vecSampleCount = (totalSampleCount / step) * step;

        for (; i < vecSampleCount; i += step)
        {
            const auto* pSrc = reinterpret_cast<const __m128i*>(pInBuffer + i);
            auto* pDst = pOutBuffer + i;

            const auto sa = _mm_loadu_si128(pSrc + 0);
            const auto sb = _mm_loadu_si128(pSrc + 1);
            const auto sc = _mm_loadu_si128(pSrc + 2);

            const auto da = _mm_srai_epi32(_mm_shuffle_epi8(sa, mask), 8);
            const auto db = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sb, sa, 12), mask), 8);
            const auto dc = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sc, sb,  8), mask), 8);
            const auto dd = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sc, sc,  4), mask), 8);

            //  Convert to float and store
            _mm_storeu_ps(pDst + 0,  _mm_mul_ps(_mm_cvtepi32_ps(da), vScale));
            _mm_storeu_ps(pDst + 4,  _mm_mul_ps(_mm_cvtepi32_ps(db), vScale));
            _mm_storeu_ps(pDst + 8,  _mm_mul_ps(_mm_cvtepi32_ps(dc), vScale));
            _mm_storeu_ps(pDst + 12, _mm_mul_ps(_mm_cvtepi32_ps(dd), vScale));
        }
    }
#endif

    for (; i < totalSampleCount; i += 1)
    {
        pOutBuffer[i] = (static_cast<s32>(pInBuffer[i])) * fScale;
    }
}
size_t DspConvertPcm_AVX2(f32* pOutBuffer, const s24* pInBuffer, size_t totalSampleCount)
{
    SFTL_ASSERT(CpuInfo::GetSupports_SIMD_I32x8());

    constexpr f32 fScale = static_cast<f32>(1.0 / (1 << 23));
    const auto vScale = _mm256_set1_ps(fScale);

    auto fnDo16Samples = [vScale](f32* pOutBuffer, const s24* pInBuffer)
    {
        const auto vScaleSSE = _mm256_castps256_ps128(vScale);
        const auto mask = _mm_setr_epi8(-1, 0, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8, -1, 9, 10, 11);

        const auto* pSrc = reinterpret_cast<const __m128i*>(pInBuffer);
        auto* pDst = pOutBuffer;

        const auto sa = _mm_loadu_si128(pSrc + 0);
        const auto sb = _mm_loadu_si128(pSrc + 1);
        const auto sc = _mm_loadu_si128(pSrc + 2);

        const auto da = _mm_srai_epi32(_mm_shuffle_epi8(sa, mask), 8);
        const auto db = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sb, sa, 12), mask), 8);
        const auto dc = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sc, sb, 8), mask), 8);
        const auto dd = _mm_srai_epi32(_mm_shuffle_epi8(_mm_alignr_epi8(sc, sc, 4), mask), 8);

        //  Convert to float and store
        _mm_storeu_ps(pDst +  0, _mm_mul_ps(_mm_cvtepi32_ps(da), vScaleSSE));
        _mm_storeu_ps(pDst +  4, _mm_mul_ps(_mm_cvtepi32_ps(db), vScaleSSE));
        _mm_storeu_ps(pDst +  8, _mm_mul_ps(_mm_cvtepi32_ps(dc), vScaleSSE));
        _mm_storeu_ps(pDst + 12, _mm_mul_ps(_mm_cvtepi32_ps(dd), vScaleSSE));
    };

    //  First 16 samples SSE style
    fnDo16Samples(pOutBuffer, pInBuffer);

    //  Next samples do AVX, where each load will discard 4 bytes at the start and end of each load
    constexpr size_t step = 16;
    const size_t vecSampleCount = ((totalSampleCount / step) * step) - 16;
    {
        const auto mask = _mm256_setr_epi8(-1, 4, 5, 6, -1, 7, 8, 9, -1, 10, 11, 12, -1, 13, 14, 15, -1, 16, 17, 18, -1, 19, 20, 21, -1, 22, 23, 24, -1, 25, 26, 27);
        for (size_t i = 16; i < vecSampleCount; i += step)
        {
            const byte* pByteBuffer = reinterpret_cast<const byte*>(pInBuffer + i);
            auto* pDst = pOutBuffer + i;

            const auto vs24_00_07 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(pByteBuffer -  4));
            const auto vs24_07_15 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(pByteBuffer - 24));

            const auto vf32_00_07 = _mm256_srai_epi32(_mm256_shuffle_epi8(vs24_00_07, mask), 8);
            const auto vf32_07_15 = _mm256_srai_epi32(_mm256_shuffle_epi8(vs24_07_15, mask), 8);

            //  Convert to float and store
            _mm256_storeu_ps(pDst + 0, _mm256_mul_ps(_mm256_cvtepi32_ps(vf32_00_07), vScale));
            _mm256_storeu_ps(pDst + 8, _mm256_mul_ps(_mm256_cvtepi32_ps(vf32_00_07), vScale));
        }
    }

    //  Last 16 samples SSE style
    fnDo16Samples(pOutBuffer + vecSampleCount, pInBuffer + vecSampleCount);

    return vecSampleCount;
}