【问题标题】:pinsrd / _mm_insert_epi32 with byte pointer alignment?pinrd / _mm_insert_epi32 与字节指针对齐?
【发布时间】:2017-09-27 09:35:35
【问题描述】:

类似于this question,我想将几​​个 24 位值收集到 SSE/AVX 寄存器的 32 位双字中。进一步:

  • 每个值与基指针的偏移量不连续
  • 每个值的偏移量只有 1 字节对齐
  • 我可以确保在每个值之后(或之前)读取向量是安全的

AVX2(高性能?)收集解决方案还可以,但我还需要预 AVX 支持。看起来带有指示 1 字节对齐的 SIB 字节的 pinrd 正是我想要的,但我不知道如何让编译器发出这个指令编码......

使用标准内在函数:

uint32_t *p = &base[offset];
vec = _mm_insert_epi32(vec, *p, 1);  // for each dword...

产生合理的编码,假设偏移对齐:

660f3a2244_b5_0001 pinsrd   $0x1, (%rbp,%rsi,4), %xmm0

但是,我想实际发出:

660f3a2244_35_0001 pinsrd   $0x1, (%rbp,%rsi), %xmm0

并手动将偏移量预乘 3。

这种编码(通过十六进制编辑链接的二进制文件进行测试)似乎工作得很好。但是......我怎么能发出它?没有多少类型转换或属性 __align__ 似乎有效。显而易见的方法:

uint8_t *p = &base[offset*3];
vec = _mm_insert_epi32(vec, *p, 1);

当然,在插入之前会将一个零扩展名的字节取消引用到一个双字。

我的内联 asm 尝试:

static inline __m128i __attribute__((always_inline))
_mm_insertu_epi32(__m128i a, void *b, long o, const int8_t imm8)
{
    __asm__("pinsrd %3, (%1, %2), %0" : "+x"(a) : "r"(b), "r"(o), "i"(imm8));
    return a;
}

产量:

660f3a22041601      pinsrd  $0x1, (%rsi,%rdx), %xmm0

这是有希望的,但似乎完全混淆了优化器;所有周围的代码都被扰乱得面目全非。

有没有办法在没有纯 asm 的情况下做到这一点? (我想使用内在的...)

另见:Dereference pointers in XMM register

【问题讨论】:

  • 为什么?整个方法大部分都被破坏了,如果您只想将打包的 24 位扩展到 32 位,请加载一堆(像素?)并打乱它们。
  • @harold,你能澄清一下“破碎”吗?您的意思是“效率低下”还是“功能不正确”(由于实际 CPU 的一些对齐限制?)至于为什么,只是将 24 位值收集到向量中以进行进一步处理。在 AVX2 之前,pinsrd 似乎是最好的指令: * 使用索引寻址从内存加载 * 到向量中的任意位置 但是,与 vpgatherdd 不同,没有办法通过 instrinsic 直接控制索引比例?如果有更好(有效,正确)的方法可以从几个未对齐的偏移量中收集到矢量元素,请教我。
  • 如果你只发布上下文可能最简单
  • @harold,它们没有打包。这就是我所说的“每个值都处于非连续偏移量”的意思。所以这是一个聚会。
  • 很公平。尽管如此,整个 scale=1 的事情是完全没有必要的,你可以将任何东西放入内在函数中,它会正常工作。最好将 movd 和 pinsrd 混合使用,以避免完全序列化 pinrd 烦人的高延迟。因此,2x movd,2x pinsrd,将两半与拆包结合起来。如果您只是 _mm_set 一切,FWIW GCC 会自动执行此操作。这不是我通常推荐的,但在这种情况下它有点道理。如果可能的话,真的应该首先避免这种情况。

标签: c bit-manipulation sse intrinsics


【解决方案1】:

@harold,谢谢。

我已经在做 movd 后跟几个 pinrd(比如 clang)。但是我在 Godbolt 上看到 clang/gcc/icc 使用各种解包模式,所以我将对其进行分析。

不幸的是,“避免聚集”不是解决方案。但你是对的,内在函数确实适用于任意对齐。简单的指针转换最终会做正确的事情(即产生一个可能未对齐的地址):

__m128i gather32_scale4(int *b, long o0, long o1, long o2, long o3)
{
    return _mm_set_epi32(b[o0], b[o1], b[o2], b[o3]);
    //  movd    xmm0, dword ptr [rdi + 4*r8]
    //  pinsrd  xmm0, dword ptr [rdi + 4*rcx], 1
    //  pinsrd  xmm0, dword ptr [rdi + 4*rdx], 2
    //  pinsrd  xmm0, dword ptr [rdi + 4*rsi], 3
}

__m128i gather32_scale1(int *b, long o0, long o1, long o2, long o3)
{
    return _mm_set_epi32(
        *(int *)&((char *)b)[o0],
        *(int *)&((char *)b)[o1],
        *(int *)&((char *)b)[o2],
        *(int *)&((char *)b)[o3]);
    //  movd    xmm0, dword ptr [rdi + r8]
    //  pinsrd  xmm0, dword ptr [rdi + rcx], 1
    //  pinsrd  xmm0, dword ptr [rdi + rdx], 2
    //  pinsrd  xmm0, dword ptr [rdi + rsi], 3
}

(与手动编写的 _mm_insert_epi32 类似)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-09-02
    • 2013-05-13
    • 2010-11-30
    • 2017-09-04
    • 2017-08-16
    • 1970-01-01
    • 2012-03-05
    • 1970-01-01
    相关资源
    最近更新 更多