Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/152.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 将16位掩码转换为16字节掩码_C++_C_Bit Manipulation_Sse_Intrinsics - Fatal编程技术网

C++ 将16位掩码转换为16字节掩码

C++ 将16位掩码转换为16字节掩码,c++,c,bit-manipulation,sse,intrinsics,C++,C,Bit Manipulation,Sse,Intrinsics,有没有办法转换以下代码: int mask16=0b1010101010101010;//int或short,有符号或无符号,都无所谓 到 __uint128_t mask128=\uu uint128_t0x010001000100100对于掩码中的每个位,您希望将位置n处的位移动到位置n处字节的低位,即位位置8*n。可以通过循环执行此操作: __uint128_t intrinsic_bits_to_bytes(uint16_t mask) { int i; __uint12

有没有办法转换以下代码:

int mask16=0b1010101010101010;//int或short,有符号或无符号,都无所谓 到


__uint128_t mask128=\uu uint128_t0x010001000100100对于掩码中的每个位,您希望将位置n处的位移动到位置n处字节的低位,即位位置8*n。可以通过循环执行此操作:

__uint128_t intrinsic_bits_to_bytes(uint16_t mask)
{
    int i;
    __uint128_t result = 0;

    for (i=0; i<16; i++) {
        result |= (__uint128_t )((mask >> i) & 1) << (8 * i);
    }
    return result;
}

对于掩码中的每个位,您希望将位置n处的位移动到位置n处字节的低位,即位位置8*n。可以通过循环执行此操作:

__uint128_t intrinsic_bits_to_bytes(uint16_t mask)
{
    int i;
    __uint128_t result = 0;

    for (i=0; i<16; i++) {
        result |= (__uint128_t )((mask >> i) & 1) << (8 * i);
    }
    return result;
}

如果可以使用AVX512,则可以在一条指令中完成,无循环:

#include <immintrin.h>

__m128i intrinsic_bits_to_bytes(uint16_t mask16) {
    const __m128i zeroes = _mm_setzero_si128();
    const __m128i ones = _mm_set1_epi8(1);;
    return _mm_mask_blend_epi8(mask16, ones, zeroes);
}
这将生成OK,但如果处理器不支持AVX512,它将在运行时抛出非法指令
时间。

如果可以使用AVX512,则可以在一条指令中完成,无循环:

#include <immintrin.h>

__m128i intrinsic_bits_to_bytes(uint16_t mask16) {
    const __m128i zeroes = _mm_setzero_si128();
    const __m128i ones = _mm_set1_epi8(1);;
    return _mm_mask_blend_epi8(mask16, ones, zeroes);
}
这将生成OK,但如果处理器不支持AVX512,它将在运行时抛出非法指令 时间。

位/字节顺序:除非另有说明,否则问题如下,将uint16的LSB置于小端x86上最低内存地址的最低有效字节中。例如,对于位图的ASCII转储,这是您想要的,但对于单个16位数字的base-2表示,这与place-value打印顺序相反

有效地将值返回到RDX:RAX整数寄存器的讨论与大多数正常使用情况无关,因为您只需将向量寄存器中的值存储到内存中,无论是0/1字节整数还是ASCII“0'/'1'数字,您都可以最有效地获取这些值,而不必在u m128i中使用0/1整数,更不用说在未签名的int128中

目录:

SSE2/SSSE3版本:如果您希望在向量中获得结果,例如用于存储字符数组,那么这很好。 ,洗牌到MSB第一个打印顺序并转换为ASCII。 BMI2 pdep:如果要在标量寄存器中使用结果,则适用于使用BMI2的英特尔CPU上的标量无符号_int128。慢对AMD。 纯C++的乘法BithACK:标量非常合理 AVX-512:AVX-512将掩蔽作为使用标量位图的一级操作。如果将结果用作标量的一半,则可能不如BMI2 pdep,否则甚至比SSSE3更好。 32位整数的最低地址转储处的AVX2打印顺序MSB。 有关元素大小和遮罩宽度的其他变化,请参见。SSE2和multiply bithack是根据该集合中链接的答案改编的。 SSE2最好是SSSE3 见@aqrit的答案

为了适应16位->16字节的情况,我们需要一个洗牌,将掩码的第一个字节复制到向量的前8个字节,将第二个掩码字节复制到高8个向量字节。使用一个SSSE3 pshufb,或者使用punpcklbw-same,same+punpcklwd-same,same+punpckldq-same,最终复制到两个64位Qword是可行的

typedef unsigned __int128  u128;

u128 mask_to_u128_SSSE3(unsigned bitmap)
{
    const __m128i shuffle = _mm_setr_epi32(0,0, 0x01010101, 0x01010101);
    __m128i v = _mm_shuffle_epi8(_mm_cvtsi32_si128(bitmap), shuffle);  // SSSE3 pshufb

    const __m128i bitselect = _mm_setr_epi8(
        1, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1U<<7,
        1, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1U<<7 );
    v = _mm_and_si128(v, bitselect);
    v = _mm_min_epu8(v, _mm_set1_epi8(1));       // non-zero -> 1  :  0 -> 0
    // return v;   // if you want a SIMD vector result

    alignas(16) u128 tmp;
    _mm_store_si128((__m128i*)&tmp, v);
    return tmp;   // optimizes to movq / pextrq (with SSE4)
}
BMI2 pdep:英特尔好,AMD坏 BMI2 pdep在Haswell之后的Intel CPU上速度很快,但在AMD上速度非常慢,超过十几个UOP,延迟很高

typedef unsigned __int128  u128;
inline u128 assemble_halves(uint64_t lo, uint64_t hi) {
    return ((u128)hi << 64) | lo; }
// could replace this with __m128i using _mm_set_epi64x(hi, lo) to see how that compiles

#ifdef __BMI2__
#include <immintrin.h>
auto mask_to_u128_bmi2(unsigned bitmap) {
    // fast on Intel, slow on AMD
    uint64_t tobytes = 0x0101010101010101ULL;
    uint64_t lo = _pdep_u64(bitmap, tobytes);
    uint64_t hi = _pdep_u64(bitmap>>8, tobytes);
    return assemble_halves(lo, hi);
}
一种带魔法乘法的二进制C++ 在x86-64上还不错;自Zen以来,AMD拥有快速64位乘法,而英特尔自Nehalem以来就拥有了这一功能。一些低功耗的CPU仍然有缓慢的imul r64,r64

此版本对于uu uint128_t结果可能是最佳的,至少对于不带BMI2的英特尔和AMD上的延迟是如此,因为它避免了到XMM寄存器的往返。但是对于吞吐量来说,它是相当多的指令

有关乘法的解释和相反方向,请参见上@phuclv的答案。每8位掩码的一半,使用unpack8bools中的算法一次

AVX-512 使用AVX-512BW很容易做到这一点;您可以将掩码用于重复0x01常量的零掩码负载

__m128i bits_to_bytes_avx512bw(unsigned mask16) {
    return _mm_maskz_mov_epi8(mask16, _mm_set1_epi8(1));

//    alignas(16) unsigned __int128 tmp;
//    _mm_store_si128((__m128i*)&u128, v);  // should optimize into vmovq / vpextrq
//    return tmp;
}
或者避免使用内存常量,因为编译器可以执行set1-1:执行零掩码绝对值-1。可提升恒定设置,与set11相同

__m128i bits_to_bytes_avx512bw_noconst(unsigned mask16) {
    __m128i ones = _mm_set1_epi8(-1);    // extra instruction *off* the critical path
    return _mm_maskz_abs_epi8(mask16, ones);
}
但请注意,如果做进一步的向量填充,maskz_mov的结果可能会优化到其他操作中。例如,vec+=maskz_mov可以优化为合并屏蔽添加。但如果不是,vmovdqu8 xmm{k}{z},xmm需要像vpabsb xmm{k}{z},xmm这样的ALU端口,但是vpabsb不能在天湖/冰湖上的端口5上运行。从零寄存器中屏蔽零的vpsubb可以避免可能的吞吐量问题,但随后您将设置2个寄存器以避免加载常量。在手工编写的asm中,如果希望避免常量的4字节广播负载,您只需要自己使用vpcmpeqd/vpabsb实现set11

__m128i bits_to_bytes_avx512bw(unsigned mask16) {
    return _mm_maskz_mov_epi8(mask16, _mm_set1_epi8(1));

//    alignas(16) unsigned __int128 tmp;
//    _mm_store_si128((__m128i*)&u128, v);  // should optimize into vmovq / vpextrq
//    return tmp;
}
带有gcc和叮当声-O3-march=skylake-avx512。Clang看穿了屏蔽的vpabsb,并使用内存常量将其编译为与第一个版本相同的版本

__m128i bits_to_bytes_avx512bw(unsigned mask16) {
    return _mm_maskz_mov_epi8(mask16, _mm_set1_epi8(1));

//    alignas(16) unsigned __int128 tmp;
//    _mm_store_si128((__m128i*)&u128, v);  // should optimize into vmovq / vpextrq
//    return tmp;
}
如果可以使用向量0/-1而不是0/1,则更好:使用return\u mm\u movm\u epi8mask16。编译为仅kmovd k0,edi/vpmovm2b xmm0,k0

如果需要ASCII字符的向量,如“0”或“1”,则可以使用_mm\u mask\u blend\u ep 任务,1,0。这应该比在set11的向量中进行合并屏蔽加法(需要额外的寄存器副本)更有效,也比set1'0'和_mm_movm_epi8mask16之间的sub更有效,后者需要两条指令:一条指令将掩码转换为向量,另一条指令是单独的vpsubb

AVX2,最低地址的位以打印顺序MSB,字节以mem顺序,ASCII“0”/“1” 使用类似此输出格式的[]分隔符和\t选项卡,从:

显然,如果您希望所有16或32个ASCII数字都是连续的,那么就更容易了,而且不需要对输出进行洗牌来分别存储每个8字节的数据块。在这里发布的主要原因是,它具有按正确顺序打印的shuffle和mask常量,并且显示了一个为ASCII输出优化的版本,而这正是问题真正想要的

使用基本上是256位版本的SSSE3代码

#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <immintrin.h>
#include <string.h>

// https://stackoverflow.com/questions/21622212/how-to-perform-the-inverse-of-mm256-movemask-epi8-vpmovmskb
void binary_dump_4B_avx2(const void *input)
{
    char buf[CHAR_BIT*4 + 2*4 + 3 + 1 + 1];  // bits, 4x [], 3x \t, \n, 0
    buf[0] = '[';
    for (int i=9 ; i<sizeof(buf) - 8; i+=11){ // GCC strangely doesn't unroll this loop
        memcpy(&buf[i], "]\t[", 4);       // 4-byte store as a single; we overlap the 0 later
    }
    __m256i  v = _mm256_castps_si256(_mm256_broadcast_ss(input));         // aliasing-safe load; use _mm256_set1_epi32 if you know you have an int
    const __m256i shuffle = _mm256_setr_epi64x(0x0000000000000000,        // low byte first, bytes in little-endian memory order
      0x0101010101010101, 0x0202020202020202, 0x0303030303030303);
    v =  _mm256_shuffle_epi8(v, shuffle);

//    __m256i bit_mask = _mm256_set1_epi64x(0x8040201008040201);    // low bits to low bytes
    __m256i bit_mask = _mm256_set1_epi64x(0x0102040810204080);      // MSB to lowest byte; printing order

    v = _mm256_and_si256(v, bit_mask);               // x & mask == mask
//    v = _mm256_cmpeq_epi8(v, _mm256_setzero_si256());       // -1  /  0  bytes
//    v = _mm256_add_epi8(v, _mm256_set1_epi8('1'));          // '0' / '1' bytes

    v = _mm256_cmpeq_epi8(v, bit_mask);              // 0 / -1  bytes
    v = _mm256_sub_epi8(_mm256_set1_epi8('0'), v);   // '0' / '1' bytes
    __m128i lo = _mm256_castsi256_si128(v);
    _mm_storeu_si64(buf+1, lo);
    _mm_storeh_pi((__m64*)&buf[1+8+3], _mm_castsi128_ps(lo));

    // TODO?: shuffle first and last bytes into the high lane initially to allow 16-byte vextracti128 stores, with later stores overlapping to replace garbage.
    __m128i hi = _mm256_extracti128_si256(v, 1);
    _mm_storeu_si64(buf+1+11*2, hi);
    _mm_storeh_pi((__m64*)&buf[1+11*3], _mm_castsi128_ps(hi));
//    buf[32 + 2*4 + 3] = '\n';
//    buf[32 + 2*4 + 3 + 1] = '\0';
//    fputs
    memcpy(&buf[32 + 2*4 + 2], "]", 2);  // including '\0'
    puts(buf);                           // appends a newline
     // appending our own newline and using fputs or fwrite is probably more efficient.
}

void binary_dump(const void *input, size_t bytecount) {
}
 // not shown: portable version, see Godbolt, or my or @chux's answer on the codereview question


int main(void)
{
    int t = 1000000;
    binary_dump_4B_avx2(&t);
    binary_dump(&t, sizeof(t));
    t++;
    binary_dump_4B_avx2(&t);
    binary_dump(&t, sizeof(t));
}
gcc-O3-march=haswell

请注意,GCC10.3和更早版本是哑的,并且复制and/CMPEQ向量常量,一次作为字节,一次作为qwords。在这种情况下,与零进行比较会更好,或者使用或与反转掩码进行比较,然后与所有掩码进行比较。GCC11.1使用.set.LC1、.LC2修复该问题,但仍将其作为内存操作数加载两次,而不是加载一次到寄存器中。Clang没有这两个问题

有趣的事实:clang-march=icelake客户端成功地将第二部分转换为“0”和“1”向量之间的AVX-512屏蔽混合,但它不只是使用kmov,而是使用广播加载、vpermb字节洗牌,然后使用位掩码测试到掩码中。

位/字节顺序:除非另有说明,否则这些都是下面的问题,将uint16的LSB放入小端x86上的uu uint128_t最低内存地址的最低有效字节中。例如,对于位图的ASCII转储,这是您想要的,但对于单个16位数字的base-2表示,这与place-value打印顺序相反

有效地将值返回到RDX:RAX整数寄存器的讨论与大多数正常使用情况无关,因为您只需将向量寄存器中的值存储到内存中,无论是0/1字节整数还是ASCII“0'/'1'数字,您都可以最有效地获取这些值,而不必在u m128i中使用0/1整数,更不用说在未签名的int128中

目录:

SSE2/SSSE3版本:如果您希望在向量中获得结果,例如用于存储字符数组,那么这很好。 ,洗牌到MSB第一个打印顺序并转换为ASCII。 BMI2 pdep:如果要在标量寄存器中使用结果,则适用于使用BMI2的英特尔CPU上的标量无符号_int128。慢对AMD。 纯C++的乘法BithACK:标量非常合理 AVX-512:AVX-512将掩蔽作为使用标量位图的一级操作。如果将结果用作标量的一半,则可能不如BMI2 pdep,否则甚至比SSSE3更好。 32位整数的最低地址转储处的AVX2打印顺序MSB。 有关元素大小和遮罩宽度的其他变化,请参见。SSE2和multiply bithack是根据该集合中链接的答案改编的。 SSE2最好是SSSE3 见@aqrit的答案

为了适应16位->16字节的情况,我们需要一个洗牌,将掩码的第一个字节复制到向量的前8个字节,将第二个掩码字节复制到高8个向量字节。使用一个SSSE3 pshufb,或者使用punpcklbw-same,same+punpcklwd-same,same+punpckldq-same,最终复制到两个64位Qword是可行的

typedef unsigned __int128  u128;

u128 mask_to_u128_SSSE3(unsigned bitmap)
{
    const __m128i shuffle = _mm_setr_epi32(0,0, 0x01010101, 0x01010101);
    __m128i v = _mm_shuffle_epi8(_mm_cvtsi32_si128(bitmap), shuffle);  // SSSE3 pshufb

    const __m128i bitselect = _mm_setr_epi8(
        1, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1U<<7,
        1, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1U<<7 );
    v = _mm_and_si128(v, bitselect);
    v = _mm_min_epu8(v, _mm_set1_epi8(1));       // non-zero -> 1  :  0 -> 0
    // return v;   // if you want a SIMD vector result

    alignas(16) u128 tmp;
    _mm_store_si128((__m128i*)&tmp, v);
    return tmp;   // optimizes to movq / pextrq (with SSE4)
}
BMI2 pdep:英特尔好,AMD坏 BMI2 pdep在Haswell之后的Intel CPU上速度很快,但在AMD上速度非常慢,超过十几个UOP,延迟很高

typedef unsigned __int128  u128;
inline u128 assemble_halves(uint64_t lo, uint64_t hi) {
    return ((u128)hi << 64) | lo; }
// could replace this with __m128i using _mm_set_epi64x(hi, lo) to see how that compiles

#ifdef __BMI2__
#include <immintrin.h>
auto mask_to_u128_bmi2(unsigned bitmap) {
    // fast on Intel, slow on AMD
    uint64_t tobytes = 0x0101010101010101ULL;
    uint64_t lo = _pdep_u64(bitmap, tobytes);
    uint64_t hi = _pdep_u64(bitmap>>8, tobytes);
    return assemble_halves(lo, hi);
}
一种带魔法乘法的二进制C++ 在x86-64上还不错;自Zen以来,AMD拥有快速64位乘法,而英特尔自Nehalem以来就拥有了这一功能。一些低功耗的CPU仍然有缓慢的imul r64,r64

此版本对于uu uint128_t结果可能是最佳的,至少对于不带BMI2的英特尔和AMD上的延迟是如此,因为它避免了到XMM寄存器的往返。但是对于吞吐量来说,它是相当多的指令

有关乘法的解释和相反方向,请参见上@phuclv的答案。每8位掩码的一半,使用unpack8bools中的算法一次

AVX-512 使用AVX-512BW很容易做到这一点;您可以将掩码用于重复0x01常量的零掩码负载

__m128i bits_to_bytes_avx512bw(unsigned mask16) {
    return _mm_maskz_mov_epi8(mask16, _mm_set1_epi8(1));

//    alignas(16) unsigned __int128 tmp;
//    _mm_store_si128((__m128i*)&u128, v);  // should optimize into vmovq / vpextrq
//    return tmp;
}
或者避免使用内存常量,因为编译器可以执行set1-1:执行零掩码绝对值-1。可提升恒定设置,与set11相同

__m128i bits_to_bytes_avx512bw_noconst(unsigned mask16) {
    __m128i ones = _mm_set1_epi8(-1);    // extra instruction *off* the critical path
    return _mm_maskz_abs_epi8(mask16, ones);
}
但请注意,如果做进一步的向量填充,maskz_mov的结果可能会优化到其他操作中。例如,vec+=maskz_mov可以优化为合并屏蔽添加。但如果不是,vmovdqu8 xmm{k}{z},xmm需要像vpabsb xmm{k}{z},xmm这样的ALU端口,但是vpabsb不能在天湖/冰湖上的端口5上运行。从零寄存器中屏蔽零的vpsubb可以避免这种可能的错误 ghput问题,但您需要设置2个寄存器以避免加载常量。在手工编写的asm中,如果希望避免常量的4字节广播负载,您只需要自己使用vpcmpeqd/vpabsb实现set11

__m128i bits_to_bytes_avx512bw(unsigned mask16) {
    return _mm_maskz_mov_epi8(mask16, _mm_set1_epi8(1));

//    alignas(16) unsigned __int128 tmp;
//    _mm_store_si128((__m128i*)&u128, v);  // should optimize into vmovq / vpextrq
//    return tmp;
}
带有gcc和叮当声-O3-march=skylake-avx512。Clang看穿了屏蔽的vpabsb,并使用内存常量将其编译为与第一个版本相同的版本

__m128i bits_to_bytes_avx512bw(unsigned mask16) {
    return _mm_maskz_mov_epi8(mask16, _mm_set1_epi8(1));

//    alignas(16) unsigned __int128 tmp;
//    _mm_store_si128((__m128i*)&u128, v);  // should optimize into vmovq / vpextrq
//    return tmp;
}
如果可以使用向量0/-1而不是0/1,则更好:使用return\u mm\u movm\u epi8mask16。编译为仅kmovd k0,edi/vpmovm2b xmm0,k0

如果需要ASCII字符的向量,如“0”或“1”,可以使用_mm_mask_blend_epi8mask、1或0。这应该比在set11的向量中进行合并屏蔽加法(需要额外的寄存器副本)更有效,也比set1'0'和_mm_movm_epi8mask16之间的sub更有效,后者需要两条指令:一条指令将掩码转换为向量,另一条指令是单独的vpsubb

AVX2,最低地址的位以打印顺序MSB,字节以mem顺序,ASCII“0”/“1” 使用类似此输出格式的[]分隔符和\t选项卡,从:

显然,如果您希望所有16或32个ASCII数字都是连续的,那么就更容易了,而且不需要对输出进行洗牌来分别存储每个8字节的数据块。在这里发布的主要原因是,它具有按正确顺序打印的shuffle和mask常量,并且显示了一个为ASCII输出优化的版本,而这正是问题真正想要的

使用基本上是256位版本的SSSE3代码

#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <immintrin.h>
#include <string.h>

// https://stackoverflow.com/questions/21622212/how-to-perform-the-inverse-of-mm256-movemask-epi8-vpmovmskb
void binary_dump_4B_avx2(const void *input)
{
    char buf[CHAR_BIT*4 + 2*4 + 3 + 1 + 1];  // bits, 4x [], 3x \t, \n, 0
    buf[0] = '[';
    for (int i=9 ; i<sizeof(buf) - 8; i+=11){ // GCC strangely doesn't unroll this loop
        memcpy(&buf[i], "]\t[", 4);       // 4-byte store as a single; we overlap the 0 later
    }
    __m256i  v = _mm256_castps_si256(_mm256_broadcast_ss(input));         // aliasing-safe load; use _mm256_set1_epi32 if you know you have an int
    const __m256i shuffle = _mm256_setr_epi64x(0x0000000000000000,        // low byte first, bytes in little-endian memory order
      0x0101010101010101, 0x0202020202020202, 0x0303030303030303);
    v =  _mm256_shuffle_epi8(v, shuffle);

//    __m256i bit_mask = _mm256_set1_epi64x(0x8040201008040201);    // low bits to low bytes
    __m256i bit_mask = _mm256_set1_epi64x(0x0102040810204080);      // MSB to lowest byte; printing order

    v = _mm256_and_si256(v, bit_mask);               // x & mask == mask
//    v = _mm256_cmpeq_epi8(v, _mm256_setzero_si256());       // -1  /  0  bytes
//    v = _mm256_add_epi8(v, _mm256_set1_epi8('1'));          // '0' / '1' bytes

    v = _mm256_cmpeq_epi8(v, bit_mask);              // 0 / -1  bytes
    v = _mm256_sub_epi8(_mm256_set1_epi8('0'), v);   // '0' / '1' bytes
    __m128i lo = _mm256_castsi256_si128(v);
    _mm_storeu_si64(buf+1, lo);
    _mm_storeh_pi((__m64*)&buf[1+8+3], _mm_castsi128_ps(lo));

    // TODO?: shuffle first and last bytes into the high lane initially to allow 16-byte vextracti128 stores, with later stores overlapping to replace garbage.
    __m128i hi = _mm256_extracti128_si256(v, 1);
    _mm_storeu_si64(buf+1+11*2, hi);
    _mm_storeh_pi((__m64*)&buf[1+11*3], _mm_castsi128_ps(hi));
//    buf[32 + 2*4 + 3] = '\n';
//    buf[32 + 2*4 + 3 + 1] = '\0';
//    fputs
    memcpy(&buf[32 + 2*4 + 2], "]", 2);  // including '\0'
    puts(buf);                           // appends a newline
     // appending our own newline and using fputs or fwrite is probably more efficient.
}

void binary_dump(const void *input, size_t bytecount) {
}
 // not shown: portable version, see Godbolt, or my or @chux's answer on the codereview question


int main(void)
{
    int t = 1000000;
    binary_dump_4B_avx2(&t);
    binary_dump(&t, sizeof(t));
    t++;
    binary_dump_4B_avx2(&t);
    binary_dump(&t, sizeof(t));
}
gcc-O3-march=haswell

请注意,GCC10.3和更早版本是哑的,并且复制and/CMPEQ向量常量,一次作为字节,一次作为qwords。在这种情况下,与零进行比较会更好,或者使用或与反转掩码进行比较,然后与所有掩码进行比较。GCC11.1使用.set.LC1、.LC2修复该问题,但仍将其作为内存操作数加载两次,而不是加载一次到寄存器中。Clang没有这两个问题


有趣的事实:clang-march=icelake客户端成功地将第二部分转换为“0”和“1”向量之间的AVX-512掩码混合,但它不只是使用kmov,而是使用广播加载、vpermb字节洗牌,然后使用位掩码测试掩码。

在int mask16=0b1010101010101010中使用int;这是一个糟糕的类型选择。考虑无符号类型,避免设置符号位和符号扩展的问题。我很困惑。16位掩码为2字节。对于剩余的14个字节,您将使用什么作为填充符?加油口在前面吗?加油口在后面吗?未使用的14个字节的模式是什么?如果您要将一位转换为一个字节,那么您希望在字节中的哪个位置设置位?Related:和@nicomp:而不是pcmpeqb,将pminub与set11一起使用,这样您可以得到0或1。这是我答案顶部的一个链接,总结了这方面的技术;这是一个糟糕的类型选择。考虑无符号类型,避免设置符号位和符号扩展的问题。我很困惑。16位掩码为2字节。对于剩余的14个字节,您将使用什么作为填充符?加油口在前面吗?加油口在后面吗?未使用的14个字节的模式是什么?如果您要将一位转换为一个字节,那么您希望在字节中的哪个位置设置位?Related:和@nicomp:而不是pcmpeqb,将pminub与set11一起使用,这样您可以得到0或1。这是我答案顶部的一个链接,上面总结了这方面的技术。嗨,弗拉德,这很有趣!你能添加编译说明吗?有了gcc,我就在寻找一种无需使用loop@AntoninGAVREL-请查看我编辑的答案。不要使用静态常量m128i,它编译的asm比常规常量m128i糟糕得多。让编译器处理它,就像处理字符串文本和FP常量一样。另请参阅使用零掩码的“mm_maskz_mov_epi8”加载1或0,而不是混合。发布了一个带有我的改进版本和非AVX-512版本的答案。@VladFeinstein:我建议至少编辑你的答案,以删除常数上的积极有害静态。在这之后,一切都很好,clang可能仍然会根据您的实际需要对其进行优化,甚至会将常量传播到带有set1'0'或其他内容的add中。嗨,Vlad,这非常有趣!你能添加编译说明吗?有了gcc,我就在寻找一种无需使用loop@AntoninGAVREL-请查看我编辑的答案。不要使用静态常量m128i,它编译的asm比常规常量m128i糟糕得多。让编译器处理它,就像处理字符串文本和FP常量一样。另请参阅使用零掩码的“mm_maskz_mov_epi8”加载1或0,而不是混合。发布了一个带有我的改进版本和非AVX-512版本的答案。@VladFeinstein:我建议至少编辑你的答案,以删除常数上的积极有害静态。在那之后,一切都会好起来,叮当声也会出现
仍然能够将其优化为您真正想要的,甚至可以使用set1'0'或其他内容将常数传播到add中。Hi-dbush,我已经使用while-I>=0 result |=u uint128_t mask>>I&1 Hi-dbush创建了此函数,我已经用while-I>=0 result |=uu uint128\u t mask>>创建了这个函数,我想知道这与使用类似的东西相比如何。例如@Noah:OP显然想要一个_int128而不是一个_m128i,因此如果我们想要返回整数regs中的结果,那么延迟会更好。但更多说明:请注意,在lo乘法之前,您没有隔离掩码的低字节,并且忘记了对乘法结果进行掩码和移位。@Noah:由于我已经在Godbolt上测试了这些示例,所以使用了经过训练的示例进行了更新。@AntoninGAVREL:整个操作的成本并不比movq/pextrq或movq+punpckhqdq/movq高多少。如果您最终想要一个以“0”或“1”为基数的ASCII字符串,从set1“0”中减去一个pcmpeqb结果是有效的,并且避免了在SSSE3版本中需要一个向量常量。@AntoninGAVREL,否,_mm_cmpeq_epi产生一个0/-1的结果。与屏蔽或以其他方式将其转换为0/1并相加不同,基于比较结果有条件地递增的常用技巧是_mm_sub_epi8x,_mm_cmpeq_epi8y,z。我在对我的答案的编辑中的一个新段落中也提到了这一点。不知道这与使用类似的东西相比如何。例如@Noah:OP显然想要一个_int128而不是一个_m128i,因此如果我们想要返回整数regs中的结果,那么延迟会更好。但更多说明:请注意,在lo乘法之前,您没有隔离掩码的低字节,并且忘记了对乘法结果进行掩码和移位。@Noah:由于我已经在Godbolt上测试了这些示例,所以使用了经过训练的示例进行了更新。@AntoninGAVREL:整个操作的成本并不比movq/pextrq或movq+punpckhqdq/movq高多少。如果您最终想要一个以“0”或“1”为基数的ASCII字符串,从set1“0”中减去一个pcmpeqb结果是有效的,并且避免了在SSSE3版本中需要一个向量常量。@AntoninGAVREL,否,_mm_cmpeq_epi产生一个0/-1的结果。与屏蔽或以其他方式将其转换为0/1并相加不同,基于比较结果有条件地递增的常用技巧是_mm_sub_epi8x,_mm_cmpeq_epi8y,z。我在对我的答案的编辑中的一个新段落中也提到了这一点。