如何使用x86 SIMD

时间:2018-08-30 13:56:28

标签: x86 bit-manipulation sse simd avx2

我想将8位整数转换为大小为8的数组,每个值都包含整数的位值。

例如:我有int8_t x = 8;,我想将其转换为int8_t array_x = {0,0,0,0,1,0,0,0};

由于此计算是信号处理模块的一部分,因此必须有效地完成此操作。有有效的方法吗?我确实检查了混合说明。当具有8位大小的数组元素时,它不符合我的要求。开发平台是AMD Ryzen。

2 个答案:

答案 0 :(得分:3)

具有0x00:0x01格式结果的单个字节的“反向移动掩码”,带有SIMD,但没有BMI2。

__m128i v = _mm_set1_epi8(bitmap); 
v = _mm_and_si128(v, _mm_set_epi32(0, 0, 0x80402010, 0x08040201));
v = _mm_min_epu8(v, _mm_set1_epi8(1));
_mm_storel_epi64((__m128i*)&array_x[0], v); 

答案 1 :(得分:1)

此答案末尾的第一个示例显示了如何使用BMI2指令pdep计算8字节数组。

请注意,在Intel Haswell处理器及更高版本上,pdep指令的吞吐量为1 每个周期的指令数和3个周期的延迟,这是很快的。在AMD Ryzen上,此说明是 相对慢的是:延迟和吞吐量都是18个周期。 对于AMD Ryzen,最好将pdep指令替换为乘法和一些按位运算,这在AMD Ryzen上非常快,请参阅此答案末尾的第二个示例。


另请参阅herehere  使用标量源进行有效的逆运动蒙版计算 以及256位AVX2矢量目标。

与其同时处理8位和8个字节,不如说是 重组算法的效率更高,每步处理4 x 8位和4 x 8字节。 在那种情况下,可以利用256位的完整AVx2向量宽度,这可能会更快。

Peter Cordes shows认为pext指令可用于 相反的方向:从8字节到8位。


带有pdep指令的代码示例:

/*  gcc -O3 -Wall -m64 -march=skylake bytetoarr.c  */

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

int main(){

    int i;
    union {
        uint8_t  a8[8];
        uint64_t a64;
    } t;
    /*  With mask = 0b0000000100......0100000001 = 0x0101010101010101    */
    /*  the input bits 0, 1, ..., 7 are expanded                         */
    /*  to the right positions of the uint64_t = 8 x uint8_t output      */
    uint64_t mask = 0x0101010101010101;

    /* example input: */
    uint8_t x = 0b01001100;

    t.a64 = _pdep_u64(x,mask);

    for (i = 0; i < 8; i++){
        printf("a[%i] = %hhu\n", i, t.a8[i]);
    }
}

输出为:

$ ./a.out
a[0] = 0
a[1] = 0
a[2] = 1
a[3] = 1
a[4] = 0
a[5] = 0
a[6] = 1
a[7] = 0

AMD Ryzen处理器的代码示例:

/*  gcc -O3 -Wall -m64 -march=skylake bytetoarr_amd.c  */

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

int main(){

    int i;
    union {
        uint8_t  a8[8];
        uint64_t a64;
    } t;

    /* example input: */
    uint8_t  x    = 0b01001100;

    uint64_t x64  = x;                 
    uint64_t x_hi = x64 & 0xFE;                                                  /* Unset the lowest bit.                        */
    uint64_t r_hi = x_hi * 0b10000001000000100000010000001000000100000010000000; /* Copy the remaining 7 bits 7 times.           */
    uint64_t r    = r_hi | x64;                                                  /* Merge the lowest bit into the result.        */
             t.a64= r & 0x0101010101010101   ;                                   /* Mask off the bits at the unwanted positions. */

    for (i = 0; i < 8; i++){
        printf("a[%i] = %hhu\n", i, t.a8[i]);
    }
}