带有 __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
就像链接的答案一样)。
它似乎与行04
到07
相关,如果我将它们全部设为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
,但当前处理器还不支持该指令集扩展。
【讨论】:
vpermb
和 vpermt2b
的吞吐量和延迟可能会更差:(。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 的英特尔内在指南解释