SSE2 按向量移动

SSE2 shift by vector

我一直在尝试在 SSE2 内在函数中通过向量实现移位,但从实验和 the intel intrinsic guide 来看,它似乎只使用向量的最低有效部分。

改写我的问题,给定一个向量 {v1, v2, ..., vn} 和一组位移 {s1, s2, ..., sn},我如何计算结果 {r1, r2, ..., rn} 这样:

r1 = v1 << s1
r2 = v2 << s2
...
rn = vn << sn

因为 _mm_sll_epi* 执行此操作:

r1 = v1 << s1
r2 = v2 << s1
...
rn = vn << s1

提前致谢。

编辑:

这是我的代码:

#include <iostream>

#include <cstdint>

#include <mmintrin.h>
#include <emmintrin.h>

namespace SIMD {

    using namespace std;

    class SSE2 {
    public:
        // flipped operands due to function arguments
        SSE2(uint64_t a, uint64_t b, uint64_t c, uint64_t d) { low = _mm_set_epi64x(b, a); high = _mm_set_epi64x(d, c); }

        uint64_t& operator[](int idx)
        {
            switch (idx) {
            case 0:
                _mm_storel_epi64((__m128i*)result, low);
                return result[0];
            case 1:
                _mm_store_si128((__m128i*)result, low);
                return result[1];
            case 2:
                _mm_storel_epi64((__m128i*)result, high);
                return result[0];
            case 3:
                _mm_store_si128((__m128i*)result, high);
                return result[1];
            }

            /* Undefined behaviour */
            return 0;
        }

        SSE2& operator<<=(const SSE2& rhs)
        {
            low  = _mm_sll_epi64(low,  rhs.getlow());
            high = _mm_sll_epi64(high, rhs.gethigh());

            return *this;
        }

        void print()
        {
            uint64_t a[2];
            _mm_store_si128((__m128i*)a, low);

            cout << hex;
            cout << a[0] << ' ' << a[1] << ' ';

            _mm_store_si128((__m128i*)a, high);

            cout << a[0] << ' ' << a[1] << ' ';
            cout << dec;
        }

        __m128i getlow() const
        {
            return low;
        }

        __m128i gethigh() const
        {
            return high;
        }
    private:
        __m128i low, high;
        uint64_t result[2];
    };
}

int main()
{
    cout << "operator<<= test: vector << vector: ";
    {
        auto x = SIMD::SSE2(7, 8, 15, 10);
        auto y = SIMD::SSE2(4, 5,  6,  7);

        x.print();
        y.print();

        x <<= y;

        if (x[0] != 112 || x[1] != 256 || x[2] != 960 || x[3] != 1280) {
            cout << "FAILED: ";
            x.print();
            cout << endl;
        } else {
            cout << "PASSED" << endl;
        }
    }

    return 0;
}

应该发生的结果是 {7 << 4 = 112, 8 << 5 = 256, 15 << 6 = 960, 10 << 7 = 1280}。结果似乎是 {7 << 4 = 112, 8 << 4 = 128, 15 << 6 = 960, 15 << 6 = 640},这不是我想要的。

希望这对你有所帮助,Jens。

如果 AVX2 可用,并且您的元素是 32 位或 64 位,则您的操作需要一个可变移位指令:vpsrlvq, (__m128i _mm_srlv_epi64 (__m128i a, __m128i count) )


对于 SSE4.1 的 32 位元素,请参阅 。根据延迟与吞吐量要求,您可以进行单独的移位,然后混合,或使用乘法(通过特殊构造的 2 的幂向量)来获得可变计数的左移,然后进行相同的计数-所有元素右移。


对于您的情况,具有运行时变量移位计数的 64 位元素:

每个 SSE 向量只有两个元素,所以我们只需要两次移位,然后合并结果(我们可以使用 pblendw 或浮点数 movsd(这可能会导致额外的在某些 CPU 上绕过延迟延迟),或者我们可以使用两次随机播放,或者我们可以执行两个 AND 和一个 OR。

__m128i SSE2_emulated_srlv_epi64(__m128i a, __m128i count)
{
    __m128i shift_low = _mm_srl_epi64(a, count);          // high 64 is garbage
    __m128i count_high = _mm_unpackhi_epi64(count,count); // broadcast the high element
    __m128i shift_high = _mm_srl_epi64(a, count_high);    // low 64 is garbage
    // SSE4.1:
    // return _mm_blend_epi16(shift_low, shift_high, 0x0F);

#if 1   // use movsd to blend
    __m128d blended = _mm_move_sd( _mm_castsi128_pd(shift_high), _mm_castsi128_pd(shift_low) );  // use movsd as a blend.  Faster than multiple instructions on most CPUs, but probably bad on Nehalem.
    return _mm_castpd_si128(blended);
#else  // SSE2 without using FP instructions:
    // if we're going to do it this way, we could have shuffled the input before shifting.  Probably not helpful though.
    shift_high = _mm_unpackhi_epi64(shift_high, shift_high);       // broadcast the high64
    return       _mm_unpacklo_epi64(shift_high, shift_low);        // combine
#endif
}

其他像 pshufd 或 psrldq 这样的洗牌可以工作,但是 punpckhqdq gets the job done without needing an immediate byte, so it's one byte shorter. SSSE3 palignr 可以从一个寄存器中获取高位元素,从另一个寄存器中获取低位元素到一个向量中,但它们会被颠倒(所以我们会需要一个 pshufd 来交换高低两半)。 shufpd 可以混合,但与 movsd 相比没有优势。

有关在两个整数指令之间使用 FP 指令的潜在旁路延迟延迟的详细信息,请参阅 Agner Fog's microarch guide。它在 Intel SnB 系列 CPU 上可能没问题,因为其他 FP 洗牌是。 (是的,movsd xmm1, xmm0 在端口 5 的洗牌单元上运行。如果不需要合并行为,即使是标量,也可以使用 movapsmovapd 进行 reg-reg 移动)。


这会编译(在 Godbolt 上使用 gcc5.3 -O3)到

    movdqa  xmm2, xmm0  # tmp97, a
    psrlq   xmm2, xmm1    # tmp97, count
    punpckhqdq      xmm1, xmm1  # tmp99, count
    psrlq   xmm0, xmm1    # tmp100, tmp99
    movsd   xmm0, xmm2    # tmp102, tmp97
    ret