带有 __256i 向量的意外 _mm256_shuffle_epi

Posted

技术标签:

【中文标题】带有 __256i 向量的意外 _mm256_shuffle_epi【英文标题】:unexpected _mm256_shuffle_epi with __256i vectors 【发布时间】:2017-10-05 09:40:14 【问题描述】:

我曾在使用__m128i 的图像转换中看到this great answer,并认为我会尝试使用 AVX2 来看看是否可以更快地得到它。任务是获取输入的 RGB 图像并将其转换为 RGBA(注意另一个问题是 BGRA,但这并没有太大的区别......)。

如果需要,我可以包含更多代码,但是这些内容变得非常冗长,而且我被困在看似非常简单的事情上。假设这段代码所有内容都是 32 字节对齐的,使用 -mavx2 编译等。

给定一个输入uint8_t *source RGB 和输出uint8_t *destination RGBA,它是这样的(只是试图用条纹填充图像的四分之一[因为这是矢量土地])。

#include <immintrin.h>
__m256i *src = (__m256i *) source;
__m256i *dest = (__m256i *) destination;

// for this particular image
unsigned width = 640;
unsigned height = 480;
unsigned unroll_N = (width * height) / 32;
for(unsigned idx = 0; idx < unroll_N; ++idx) 
    // Load first portion and fill all of dest[0]
    __m256i src_0 = src[0];
    __m256i tmp_0 = _mm256_shuffle_epi8(src_0,
        _mm256_set_epi8(
            0x80, 23, 22, 21,// A07 B07 G07 R07
            0x80, 20, 19, 18,// A06 B06 G06 R06
            0x80, 17, 16, 15,// A05 B05 G05 R05
            0x80, 14, 13, 12,// A04 B04 G04 R04
            0x80, 11, 10,  9,// A03 B03 G03 R03
            0x80,  8,  7,  6,// A02 B02 G02 R02
            0x80,  5,  4,  3,// A01 B01 G01 R01
            0x80,  2,  1,  0 // A00 B00 G00 R00
        )
    );

    dest[0] = tmp_0;

    // move the input / output pointers forward
    src  += 3;
    dest += 4;
// end for

这甚至不起作用。每个“季度”都会出现条纹。

我的理解是0x80应该用来在掩码中创建0x00 那里得到什么值并不重要(它是 alpha 通道,在实际代码中,它得到 OR'd 和 0xff 就像链接的答案一样)。 它似乎与行0407 相关,如果我将它们全部设为0x80 只留下00-03,那么不一致就会消失。 当然,我不会复制我需要的所有内容。

我在这里缺少什么?就像我可能用完了寄存器或其他什么?我会对此感到非常惊讶...

使用

_mm256_set_epi8(
    // 0x80, 23, 22, 21,// A07 B07 G07 R07
    // 0x80, 20, 19, 18,// A06 B06 G06 R06
    // 0x80, 17, 16, 15,// A05 B05 G05 R05
    // 0x80, 14, 13, 12,// A04 B04 G04 R04
    0x80, 0x80, 0x80, 0x80,
    0x80, 0x80, 0x80, 0x80,
    0x80, 0x80, 0x80, 0x80,
    0x80, 0x80, 0x80, 0x80,
    0x80, 11, 10,  9,// A03 B03 G03 R03
    0x80,  8,  7,  6,// A02 B02 G02 R02
    0x80,  5,  4,  3,// A01 B01 G01 R01
    0x80,  2,  1,  0 // A00 B00 G00 R00
)

【问题讨论】:

你做了src += 3,但你每次迭代只处理 一个 事情,那是刚刚过去的 2/3 是的,为了简洁起见,我省略了执行其他所有操作的代码。这就是“四分之一”的意思 x0 好的,不是很清楚。无论如何,_mm256_shuffle_epi8 不是_mm_shuffle_epi8 的概括,它就像两个_mm_shuffle_epi8 并排。因此,将索引设置为 16 及以上是没有用的。 有时 256b 向量并不是一个胜利,特别是如果你需要不止一个 vpermq 来纠正车道内的行为。与 SSE4.2 相比,AVX 仍然有帮助,因为 3 操作数指令减少了前端瓶颈。 (避免了很多 MOVDQA 指令)。英特尔 Haswell 及更高版本(即英特尔 AVX2 CPU)只有 1 个 shuffle 端口,但每个时钟可以运行 2 个负载和 1 个存储,因此对于这样的东西,您经常会遇到 shuffle 吞吐量的瓶颈。使用移位或未对齐的负载来替换 shuffle 有时会有所帮助。 (参见英特尔的优化手册和***.com/tags/x86/info) 好的,这是怎么回事:加载一个 128b 片段,然后 vinserti128 下一次迭代中的相应片段(从内存中插入不计为随机播放)并有效地使用循环的 SSSE3 版本,但使用一次两次迭代。不过可能是商店的瓶颈.. 【参考方案1】:

_mm256_shuffle_epi8_mm_shuffle_epi8 的两倍并排工作,而不是像更有用(但可能更高延迟)的全宽 shuffle 可以将任何字节放在任何地方。这是www.officedaytime.com/simd512e的图表:

AVX512VBMI 具有可以跨通道的新字节粒度 shuffle,例如 vpermb,但当前处理器还不支持该指令集扩展。

【讨论】:

vpermbvpermt2b 的吞吐量和延迟可能会更差:(。skylake-avx512 上的vpermt2w 是 3 uops (p0 + 2p5),7c 延迟 2c 吞吐量。@987654323 @ 用于来自 IACA 的电子表格。因此,in-lane + vpermq 至少同样快,并且在某些情况下可以完成工作。(例如,对于 packsswb。事实证明,vpmovwb 对于 p5 也是 2 微秒,即使它是产生半角结果的 1 输入 shuffle。所有的 pvmov shuffle 都是 2 uop,除了 vpmovqd,唯一一个具有 dword 目标元素大小和截断而不是饱和的。) vpermt2d/q 是 SKX 上的单 uop 3c 延迟。一般来说,只有穿越车道的小元素洗牌(包括vpermw)才是更多的微指令。也许在遥远的未来,一些 CPU 甚至会实现最复杂的单微指令,或者至少有更多的随机端口,但有时使用更多指令但有时使用更少微指令可能是值得的。我最近意识到,带有合并屏蔽的 shuffle 为您提供了有限形式的 2-input shuffle,这非常酷(除非需要额外的 ALU uop 来合并)。不过,我还没有找到任何很好的用例。

以上是关于带有 __256i 向量的意外 _mm256_shuffle_epi的主要内容,如果未能解决你的问题,请参考以下文章

试图理解 _mm256_permute2x128_si256 的英特尔内在指南解释

将 __m256i 存储为整数

将 __m256i 存储为整数

有没有办法用 AVX2 编写 _mm256_shldi_epi8(a,b,1) ? (向量之间每 8 位元素移位一位)

int64_t 指针转换为 AVX2 内在 _m256i

英特尔 SIMD 内在函数:_mm256_i64scatter_pd