X86 使用SIMD SSE…AVX的QWORD无序顺序7位到字节对齐

X86 使用SIMD SSE…AVX的QWORD无序顺序7位到字节对齐,x86,bit-manipulation,sse,simd,avx,X86,Bit Manipulation,Sse,Simd,Avx,我想知道在任何SIMD指令系列中是否可以执行以下操作 我有一个qword输入,有63个有效位(从不为负)。从LSB开始的每个连续的7位被洗牌对齐到一个字节,左填充为1(最重要的非零字节除外)。为了说明,为了清晰起见,我将使用字母 结果只有有效字节,因此大小为0-9,转换为字节数组 In: 0|kjihgfe|dcbaZYX|WVUTSRQ|PONMLKJ|IHGFEDC|BAzyxwv|utsrqpo|nmlkjih|gfedcba Out: 0kjihgfe|1dcbaZYX|

我想知道在任何SIMD指令系列中是否可以执行以下操作

我有一个qword输入,有63个有效位(从不为负)。从LSB开始的每个连续的7位被洗牌对齐到一个字节,左填充为1(最重要的非零字节除外)。为了说明,为了清晰起见,我将使用字母

结果只有有效字节,因此大小为0-9,转换为字节数组

In:         0|kjihgfe|dcbaZYX|WVUTSRQ|PONMLKJ|IHGFEDC|BAzyxwv|utsrqpo|nmlkjih|gfedcba
Out: 0kjihgfe|1dcbaZYX|1WVUTSRQ|1PONMLKJ|1IHGFEDC|1BAzyxwv|1utsrqpo|1nmlkjih|1gfedcba
尺寸=9

In:  00|nmlkjih|gfedcba
Out: |0nmlkjih|1gfedcba
尺寸=2

我知道填料是分开的。洗牌是我的问题。 这可能吗

编辑2

这是我的更新代码。在单线程Core 2 Duo 2 GHz 64位上获取46 M/s的持续随机长度输入

private static int DecodeIS8(long j, ref byte[] result)
{
    if (j <= 0)
    {
        return 0;
    }

    int size;

    // neater code: gives something to break out of
    while (true)
    {
        result[0] = (byte)((j & 0x7F) | 0x80);
        size = 0;
        j >>= 7;

        if (j == 0) break;

        result[1] = (byte)((j & 0x7F) | 0x80);
        size++;
        j >>= 7;

        if (j == 0) break;

        result[2] = (byte)((j & 0x7F) | 0x80);
        size++;
        j >>= 7;

        if (j == 0) break;

        result[3] = (byte)((j & 0x7F) | 0x80);
        size++;
        j >>= 7;

        if (j == 0) break;

        result[4] = (byte)((j & 0x7F) | 0x80);
        size++;
        j >>= 7;

        if (j == 0) break;

        result[5] = (byte)((j & 0x7F) | 0x80);
        size++;
        j >>= 7;

        if (j == 0) break;

        result[6] = (byte)((j & 0x7F) | 0x80);
        size++;
        j >>= 7;

        if (j == 0) break;

        result[7] = (byte)((j & 0x7F) | 0x80);
        size++;
        j >>= 7;

        if (j == 0) break;

        result[8] = (byte)j;

        return 9;
    }

    result[size] ^= 0x80;

    return size + 1;
}
private static int DecodeIS8(长j,参考字节[]结果)
{
如果(j>=7;
如果(j==0)中断;
结果[1]=(字节)((j&0x7F)| 0x80);
大小++;
j>>=7;
如果(j==0)中断;
结果[2]=(字节)((j&0x7F)| 0x80);
大小++;
j>>=7;
如果(j==0)中断;
结果[3]=(字节)((j&0x7F)| 0x80);
大小++;
j>>=7;
如果(j==0)中断;
结果[4]=(字节)((j&0x7F)| 0x80);
大小++;
j>>=7;
如果(j==0)中断;
结果[5]=(字节)((j&0x7F)| 0x80);
大小++;
j>>=7;
如果(j==0)中断;
结果[6]=(字节)((j&0x7F)| 0x80);
大小++;
j>>=7;
如果(j==0)中断;
结果[7]=(字节)((j&0x7F)| 0x80);
大小++;
j>>=7;
如果(j==0)中断;
结果[8]=(字节)j;
返回9;
}
结果[大小]^=0x80;
返回大小+1;
}

是的,可以使用MMX/SSE的
pmullw
指令(固有功能:
\u mm\u mullo\u pi16
)进行每元素移位

其基本思想是使用AND指令提取交替的7位元素,并执行
pmullw
将元素移位到位。这将完成一半元素的任务,因此该过程需要用两个额外移位来重复

#include <stdio.h>
#include <stdint.h>
#include <mmintrin.h>

__m64 f(__m64 input) {
    static const __m64 mask = (__m64) 0xfe03f80fe03f80UL;
    static const __m64 multiplier = (__m64) 0x0080002000080002UL;

    __m64 t0 = _mm_and_si64 (input, mask);
    __m64 t1 = _mm_and_si64 (_mm_srli_si64 (input, 7), mask);

    t0 = _mm_mullo_pi16 (t0, multiplier);
    t1 = _mm_mullo_pi16 (t1, multiplier);

    __m64 res =  _mm_or_si64 (t0, _mm_slli_si64 (t1, 8));
    /* set most significant bits, except for in most significant byte */
    return _mm_or_si64 (res, (__m64) 0x0080808080808080UL);
}

int main(int argc, char *argv[])
{
    int i;
    typedef union {
            __m64 m64;
            unsigned char _8x8[8];
    } type_t;

    /* 0x7f7e7c7870608080 = {127, 63, 31, 15, 7, 3, 2, 1, 0} */
    type_t res0 = { .m64 = f((__m64) 0x7f7e7c7870608080UL) };

    for (i = 0; i < 8; i++) {
            printf("%3u ", res0._8x8[i]);
    }
    puts("");

    return 0;
}
意识到

00000000|dcbaZYX0 needs to be shifted by 7 (or multiplied by 2^7, 128, 0x0080)
000000PO|NMLKJ000 needs to be shifted by 5 (or multiplied by 2^5,  32, 0x0020)
0000BAzy|xwv00000 needs to be shifted by 3 (or multiplied by 2^3,   8, 0x0008)
00nmlkji|h0000000 needs to be shifted by 1 (or multiplied by 2^1,   2, 0x0002)
此函数一次写入8个字节(而不是9个7位元素将解包到的9个字节),因此每次迭代后,您只需将源指针提前7个字节。因此,到SSE2的转换稍微复杂一些

我认为不可能为
t1
使用不同的掩码和乘法器来避免移位,因为
t1
的元素将跨越16位边界,这将阻止
pmullw
工作。但是,仍有可能以某种方式进行优化

我还没有对此进行基准测试,但我怀疑它比标量版本快得多。如果您对其进行基准测试,请发布结果。我非常有兴趣看到它们


总之,算法是2个移位、2个OR、2个and和2个乘法(以及一些移动)生成8字节。

这当然是可行的,但会很难看。你需要一系列移位和掩码操作。你100%确定这是一个性能瓶颈吗?唯一可以确定的方法是编写标量版本和SIMD版本并对它们进行基准测试。如果你没有与此unp一起执行其他SIMD操作尽管如此,我怀疑您不会获得太多好处。您没有提到是否正在对该数据执行其他SIMD操作。如果您只是从内存加载打包数据并将未打包的数据存储回内存,那么SIMD不太可能有帮助。不过,我会先尽可能优化现有的标量代码,然后考虑到SIMD。这似乎有点慢-每次解码大约50个时钟-我希望标量代码可以优化以运行得更快。遗憾的是,您没有在POWER/PowerPC上执行此操作-AltiVec有128位移位。但我认为与标量代码相比,使用SSE在更少的指令中仍然可行-祝您好运,anyway!非常好!我知道这是可能的。谢谢。我相信需要一个bsl来检测MSB,以便正确设置每个字节的高位。您可以通过使用
PANDN
反转掩码来保存insn,而不是将数据移到与掩码对齐的位置,然后再移回来(再移1位)吗?或者可能只是通过使用两个不同的掩码和两个不同的乘法器(可能只有在循环非常热和/或运行在大缓冲区上时才值得)。将输入分为两半非常好的主意,允许
mul
在不踩到相邻元素的情况下移动内容。
00000000|dcbaZYX0 needs to be shifted by 7 (or multiplied by 2^7, 128, 0x0080)
000000PO|NMLKJ000 needs to be shifted by 5 (or multiplied by 2^5,  32, 0x0020)
0000BAzy|xwv00000 needs to be shifted by 3 (or multiplied by 2^3,   8, 0x0008)
00nmlkji|h0000000 needs to be shifted by 1 (or multiplied by 2^1,   2, 0x0002)