【问题标题】:How to efficiently convert an 8-bit bitmap to array of 0/1 integers with x86 SIMD [duplicate]如何使用 x86 SIMD 有效地将 8 位位图转换为 0/1 整数数组 [重复]
【发布时间】:2019-02-05 12:11:00
【问题描述】:

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

例如:我有int8_t x = 8; 我想把它转换成int8_t array_x = {0,0,0,0,1,0,0,0};

这必须高效完成,因为此计算是信号处理模块的一部分。有没有一种有效的方法来做到这一点?我确实检查了混合说明。当具有 8 位大小的数组元素时,它不符合我的要求。开发平台是 AMD Ryzen。

【问题讨论】:

  • 由于数组只有 64 位长,我会使用 BMI2 pdep 指令,或 _pdep_u64
  • @harold:毕竟不是重复的; \@aqrit 用 PAND / PMINUB 获得 0/1 的好答案不适用于您想要 0/-1 的地方,并且与使用它和掩码相比,这是一个不明显的优化。

标签: x86 bit-manipulation sse simd avx2


【解决方案1】:

具有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); 

【讨论】:

【解决方案2】:

本答案末尾的第一个示例显示了如何使用 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]);
    }
}

【讨论】:

  • 对于未来的读者,请注意pdep 在 Ryzen(或者我认为也有它的 Excavator)上效率不高。此外,如果您不希望源代码中有一个巨大的二进制常量,则掩码为 0x0101010101010101
  • 我确实检查了 pdep 指令。正如@PeterCordes 提到的那样,它非常昂贵。根据 agner 的手册,在 Ryzen 中它有 18 个延迟周期。
  • @yadhu:但在英特尔上它非常高效,尤其是如果您希望结果是整数而不是 SIMD 向量。
  • @PeterCordes 有时,当您将常量编写为二进制文件时,代码会变得更加自我解释。事实并非如此,我承认 :-) .
猜你喜欢
  • 2011-10-12
  • 1970-01-01
  • 1970-01-01
  • 2014-02-21
  • 2015-01-30
  • 1970-01-01
  • 2021-03-06
  • 2012-07-01
  • 1970-01-01
相关资源
最近更新 更多