X86 _AVX2中的毫米对齐器epi8(PALIGNR)等效物

X86 _AVX2中的毫米对齐器epi8(PALIGNR)等效物,x86,simd,intrinsics,avx,avx2,X86,Simd,Intrinsics,Avx,Avx2,在SSE3中,PALIGNR指令执行以下操作: PALIGNR将目标操作数(第一个操作数)和源操作数(第二个操作数)连接成一个中间组合,以字节粒度将组合向右移动一个常量立即数,并将右对齐的结果提取到目标中 我目前正在移植我的SSE4代码以使用AVX2指令,并处理256位寄存器而不是128位寄存器。 我天真地认为,intrinsics函数\u mm256\u aligner\u epi8(vpaligner)只在256位寄存器上执行与\u mm\u aligner\u epi8相同的操作。然而不幸

在SSE3中,PALIGNR指令执行以下操作:

PALIGNR将目标操作数(第一个操作数)和源操作数(第二个操作数)连接成一个中间组合,以字节粒度将组合向右移动一个常量立即数,并将右对齐的结果提取到目标中

我目前正在移植我的SSE4代码以使用AVX2指令,并处理256位寄存器而不是128位寄存器。 我天真地认为,intrinsics函数
\u mm256\u aligner\u epi8
(vpaligner)只在256位寄存器上执行与
\u mm\u aligner\u epi8
相同的操作。然而不幸的是,事实并非如此。事实上,
\u mm256\u aligner\u epi8
将256位寄存器视为2个128位寄存器,并对两个相邻的128位寄存器执行2个“对齐”操作。有效地执行与
\u mm\u aligner\u epi8
相同的操作,但同时在两个寄存器上执行。这里最清楚地说明了这一点:

目前,我的解决方案是继续使用
\u mm\u aligner\u epi8
,将ymm(256bit)寄存器拆分为两个xmm(128位)寄存器(高和低),如下所示:

__m128i xmm_ymm1_hi = _mm256_extractf128_si256(ymm1, 0);
__m128i xmm_ymm1_lo = _mm256_extractf128_si256(ymm1, 1);
__m128i xmm_ymm2_hi = _mm256_extractf128_si256(ymm2, 0);
__m128i xmm_ymm_aligned_lo = _mm_alignr_epi8(xmm_ymm1_lo, xmm_ymm1_hi, 1);
__m128i xmm_ymm_aligned_hi = _mm_alignr_epi8(xmm_ymm2_hi, xmm_ymm1_lo, 1);
__m256i xmm_ymm_aligned = _mm256_set_m128i(xmm_ymm_aligned_lo, xmm_ymm_aligned_hi);
这是可行的,但必须有更好的方法,对吗?
是否有更“通用”的AVX2指令可以用来获得相同的结果?

我能想到的唯一解决方案是:

静态内联m256i mm256对准器epi8(常数m256i v0,常数m256i v1,常数int n)
{
如果(n<16)
{
__m128i v0h=_mm256_extractf128_si256(v0,0);
__m128i v0l=_mm256_extractf128_si256(v0,1);
__m128i v1h=_mm256_extractf128_si256(v1,0);
__m128i-vouth=_-mm_-aligner_-epi8(v0l,v0h,n);
__m128i voutl=_-mm_-aligner_-epi8(v1h,v0l,n);
__m256i vout=_mm256_set_m128i(voutl,vouth);
退票;
}
其他的
{
__m128i v0h=_mm256_extractf128_si256(v0,1);
__m128i v0l=_mm256_extractf128_si256(v1,0);
__m128i v1h=_mm256_extractf128_si256(v1,1);
__m128i-vouth=_-mm_-aligner_-epi8(v0l,v0h,n-16);
__m128i voutl=毫米对准器epi8(v1h,v0l,n-16);
__m256i vout=_mm256_set_m128i(voutl,vouth);
退票;
}
}

我认为这与您的解决方案几乎相同,只是它还处理>=16字节的移位。

您使用
palignr
做什么?如果只是为了处理数据错位,只需使用错位负载即可;它们在现代Intelµ-体系结构上通常“足够快”(并将为您节省大量代码)

如果出于其他原因需要类似于paignr的行为,您可以简单地利用未对齐的加载支持,以无分支的方式来完成。除非您完全受加载存储限制,否则这可能是首选习惯用法

static inline __m256i _mm256_alignr_epi8(const __m256i v0, const __m256i v1, const int n)
{
    // Do whatever your compiler needs to make this buffer 64-byte aligned.
    // You want to avoid the possibility of a page-boundary crossing load.
    char buffer[64];

    // Two aligned stores to fill the buffer.
    _mm256_store_si256((__m256i *)&buffer[0], v0);
    _mm256_store_si256((__m256i *)&buffer[32], v1);

    // Misaligned load to get the data we want.
    return _mm256_loadu_si256((__m256i *)&buffer[n]);
}

如果您能提供更多关于如何准确使用
palignr
的信息,我可能会更有帮助。

我们需要两条说明:“vperm2i128”和“vpalignr”将“palignr”扩展到256位


请参阅:

是的,这是相同的解决方案。但是如果这是唯一的方法,那么AVX2指令的设计者似乎有一个很大的疏忽,我无法编译它。。。我在下面的一行中得到了编译错误:“灾难性错误:内在参数必须是立即值:”\uuum128i vouth=\umm\ualigner\uepi8(v0l,v0h,n);”。假设,因为n不是immidate。你怎么能绕过这个?我使用英特尔C++编译程序并不是真正的疏忽;这就是AVX的实现方式。大多数指令将256位寄存器视为两个独立的128位寄存器。我认为这使得迁移和与SSE的向后兼容性更容易实现。继续Jason所写的,
palignr
对于处理未对齐的数据来说是一种非常不成熟的方法(因为移位量是即时的,不是由寄存器提供的)。英特尔似乎已经意识到了这一点,并简单地使不对中数据的访问速度足够快,以至于(大部分)不再是问题。@PaulR:为此,您仍然可以在AVX寄存器的两半上独立使用
palignr
,使用与SSE相同的算法——只需在寄存器的两半部分中执行两个独立的批处理。我同意完全32B移位会很好,但从面积/功率/复杂度来看,这显然是不合理的。Intel完全有可能添加了该操作,但其效率低于未对齐负载解决方案。考虑到Sandy Bridge中的负载带宽增加了一倍,这是一个非常合理的解决方案。延迟不会很好,因为负载将从Intel CPU上的存储转发暂停中额外延迟约10个周期。IDK if存储转发暂停是一个吞吐量问题。它们可能不是。@PeterCordes:没有吞吐量风险,只有延迟。在可以提升存储以隐藏延迟,或者可以重新使用存储的数据来提取各种不同的对齐方式的情况下,这里概述的方法是有意义的。当然,我们在AVX-512中有两个源代码混洗,这通常是一个更好的解决方案。哦,好的一点,这对于在相同的两个向量上生成不同的窗口是非常好的。它也适用于运行时变量移位计数。