【问题标题】:Looking for sse 128 bit shift operation for non-immediate shift value寻找非立即移位值的 sse 128 移位操作
【发布时间】:2012-04-02 17:17:42
【问题描述】:

固有的_mm_slli_si128 将对 128 位寄存器进行逻辑左移,但仅限于立即移位值,并且按字节而不是位进行移位。

我可以使用像_mm_sll_epi64_mm_sll_epi32 这样的内在函数来左移__m128i 寄存器中的一组值,但这些不带有“溢出”位。

对于 N 位的移位,想象一下我可以执行以下操作:

  • _mm_sll_epi64
  • _mm_srr_epi64(对于我要携带的位:将它们移到低位)
  • 随机播放 srr 结果
  • 或这些一起。

(但可能还必须包括对 N 相对于 64 的检查)。

有没有更好的办法?

【问题讨论】:

  • 我认为没有更好的方法。我为这个问题的最近重复写了一个答案:stackoverflow.com/q/34478328/224132。对于 compile-time-constant 计数,它变成 4 个 insns,或 2 个 count >= 64 的 insns。对于变量 count,它分支并必须 movd 计数和从整数到向量寄存器的 64 个计数。如果数据已经在整数寄存器中,__uint128_t 在这种情况下会更好。

标签: c++ c sse


【解决方案1】:

这不是您理想的解决方案,但如果您想将 SSE 寄存器旋转或移位 8 的倍数,那么 PSHUFB 指令(和 _mm_shuffle_epi8() 内在函数)可以提供帮助。它需要第二个 SSE 寄存器作为输入;寄存器中的每个字节都有一个值,用于索引第一个输入寄存器中的字节。

【讨论】:

  • 我认为 OP 明确表示他想要位粒度而不限于立即数。 _mm_shuffle_epi8() 既是字节粒度,又需要立即数。
  • 我知道他想要位粒度,因此我的回答中的第一个子句。此外,_mm_shuffle_epi8() 不需要立即;第二个参数是__m128i 值。 See the documentation here.
  • 我应该注意这个功能需要SSSE3支持,如果你想在旧机器上运行可能不够。
  • @Mysticial:Jason 是对的,pshufb 具有 16 个预先计算的值,可用于模拟变量的字节移位。在这种情况下,可以将其用于 qword 明智的移位(当然是 0 或 1 qword ;-)),剩余的 64 位移位可以按照 OP 的建议完成。
【解决方案2】:

这是在unusual C preprocessor uses 上的(我的)博客文章中的一个附带问题。 对于 127 种不同的移位偏移,有四种不同的 SSE2 指令最佳序列用于移位。预处理器使得构造一个相当于 129 路 switch 语句的移位函数变得合理。请原谅这里的原始代码;我不熟悉直接在这里发布代码。 查看博文以了解正在发生的事情。

#include <emmintrin.h>

typedef __m128i XMM;
#define xmbshl(x,n)  _mm_slli_si128(x,n) // xm <<= 8*n  -- BYTE shift left
#define xmbshr(x,n)  _mm_srli_si128(x,n) // xm >>= 8*n  -- BYTE shift right
#define xmshl64(x,n) _mm_slli_epi64(x,n) // xm.hi <<= n, xm.lo <<= n
#define xmshr64(x,n) _mm_srli_epi64(x,n) // xm.hi >>= n, xm.lo >>= n
#define xmand(a,b)   _mm_and_si128(a,b)
#define xmor(a,b)    _mm_or_si128(a,b)
#define xmxor(a,b)   _mm_xor_si128(a,b)
#define xmzero       _mm_setzero_si128()

XMM xm_shl(XMM x, unsigned nbits)
{
    // These macros generate (1,2,5,6) SSE2 instructions, respectively:
    #define F1(n) case 8*(n): x = xmbshl(x, n); break;
    #define F2(n) case n: x = xmshl64(xmbshl(x, (n)>>3), (n)&15); break;
    #define F5(n) case n: x = xmor(xmshl64(x, n), xmshr64(xmbshl(x, 8), 64-(n))); break;
    #define F6(n) case n: x = xmor(xmshl64(xmbshl(x, (n)>>3), (n)&15),\
                                  xmshr64(xmbshl(x, 8+((n)>>3)), 64-((n)&155))); break;
    // These macros expand to 7 or 49 cases each:
    #define DO_7(f,x) f((x)+1) f((x)+2) f((x)+3) f((x)+4) f((x)+5) f((x)+6) f((x)+7)
    #define DO_7x7(f,y) DO_7(f,(y)+1*8) DO_7(f,(y)+2*8) DO_7(f,(y)+3*8) DO_7(f,(y)+4*8) \
                                        DO_7(f,(y)+5*8) DO_7(f,(y)+6*8) DO_7(f,(y)+7*8)
    switch (nbits) {
    case 0: break;
    DO_7(F5, 0) // 1..7
    DO_7(F1, 0) // 8,16,..56
    DO_7(F1, 7) // 64,72,..120
    DO_7x7(F6, 0) // 9..15 17..23 ... 57..63 i.e. [9..63]\[16,24,..,56]
    DO_7x7(F2,56) // 65..71 73..79 ... 121..127 i.e. [65..127]\[64,72,..,120]
    default: x = xmzero;
    }
    return x;
}

xm_shr 相当于上述内容,但在 F[1256] 宏中到处交换“shl”和“shr”。 HTH。

【讨论】:

  • 实际上,上面的代码对于大约一半的移位值不起作用。我针对 128 位整数的标准移位对其进行了测试(gcc 支持 __uint128_t),结果明显不同。例如,所有高于 120 的移位只是将所有位归零。
  • 对于编译时常数移位计数,您永远不需要超过 4 条指令(或 5 条没有 AVX:额外的 movdqa)。对于 count OR 带有psllq xmm0, 64 的进位。我用if 编写了它,它可以很好地编译为编译时常量计数:goo.gl/O14GhI。见stackoverflow.com/a/34482688/224132
  • 要修复代码,只需将每个 &15 或 &155 表达式替换为 &7。这就是说,这段代码非常慢(你知道分支吗?!),而且 Peter Cordes 的建议看起来更有希望。
  • :-) 该代码起源于一个示例,它通过单个开关将 c 预处理器用于类似模板的代码的扩展。感谢您深入了解它。不久前,我发现使用具有可变移位计数的直接 int64 更快。但我会修复示例代码。
猜你喜欢
  • 1970-01-01
  • 2011-10-03
  • 1970-01-01
  • 2022-11-17
  • 2012-03-10
  • 2016-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多