请告诉我,我自己也弄不明白:

这里有__m128i个SIMD向量——16个字节中的每个字节都包含以下值:

1 0 1 1 0 1 0 1 1 1 0 1 0 1 0 1

有没有可能通过某种方式变换这个向量,使所有的1都被移除,而零的位置就是这个零的向量中元素的个数.就是这样:

0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15
                                                            
1   0   1   1   0   1   0   1   1   1   0   1   0   1   0   1
                                                            
    1           4       6               10      12     14   

最后得到一个只有这些值的向量:

1  4  6  10  12  14

获得这样一个结果的逻辑是什么?应使用哪些SIMD指令?

PS:我刚刚开始学习SIMD,所以我知道的不多.我不明白.

推荐答案

如果您有BMI2个,请使用以下版本.

__m128i compressZeroIndices_bmi2( __m128i v )
{
    const __m128i zero = _mm_setzero_si128();
    // Replace zeros with 0xFF
    v = _mm_cmpeq_epi8( v, zero );

    // Extract low/high pieces into scalar registers for PEXT instruction
    uint64_t low = (uint64_t)_mm_cvtsi128_si64( v );
    uint64_t high = (uint64_t)_mm_extract_epi64( v, 1 );

    // Count payload bytes in the complete vector
    v = _mm_sub_epi8( zero, v );
    v = _mm_sad_epu8( v, zero );
    v = _mm_add_epi64( v, _mm_srli_si128( v, 8 ) );
    v = _mm_shuffle_epi8( v, zero );
    // Make a mask vector filled with 0 for payload bytes, 0xFF for padding
    const __m128i identity = _mm_setr_epi8( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
    v = _mm_max_epu8( v, identity );
    __m128i mask = _mm_cmpeq_epi8( v, identity );

    // The following line requires C++/20
    // If you don't have it, use #ifdef _MSC_VER to switch between __popcnt64() and _popcnt64() intrinsics.
    uint64_t lowBits = std::popcount( low );
    // Use BMI2 to gather these indices
    low = _pext_u64( 0x0706050403020100ull, low );
    high = _pext_u64( 0x0F0E0D0C0B0A0908ull, high );

    // Merge payload into a vector
    v = _mm_cvtsi64_si128( low | ( high << lowBits ) );
    v = _mm_insert_epi64( v, high >> ( 64 - lowBits ), 1 );

    // Apply the mask to set unused elements to -1, enables pmovmskb + tzcnt to find the length
    return _mm_or_si128( v, mask );
}

这是另一个没有BMI2的版本.在大多数CPU上可能速度较慢,但代码要简单得多,并且不使用任何标量指令.

inline __m128i sortStep( __m128i a, __m128i perm, __m128i blend )
{
    // The min/max are independent and their throughput is 0.33-0.5 cycles,
    // so this whole function only takes 3 (AMD) or 4 (Intel) cycles to complete
    __m128i b = _mm_shuffle_epi8( a, perm );
    __m128i i = _mm_min_epu8( a, b );
    __m128i ax = _mm_max_epu8( a, b );
    return _mm_blendv_epi8( i, ax, blend );
}

__m128i compressZeroIndices( __m128i v )
{
    // Replace zeros with 0-based indices, ones with 0xFF
    v = _mm_cmpgt_epi8( v, _mm_setzero_si128() );
    const __m128i identity = _mm_setr_epi8( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
    v = _mm_or_si128( v, identity );

    // Sort bytes in the vector with a network
    // https://demonstrations.wolfram.com/SortingNetworks/
    // Click the "transposition" algorithm on that demo
    const __m128i perm1 = _mm_setr_epi8( 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14 );
    const __m128i blend1 = _mm_set1_epi16( (short)0xFF00 );
    const __m128i perm2 = _mm_setr_epi8( 0, 2, 1, 4, 3, 6, 5, 8, 7, 10, 9, 12, 11, 14, 13, 15 );
    const __m128i blend2 = _mm_setr_epi8( 0, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0 );
    for( size_t i = 0; i < 8; i++ )
    {
        v = sortStep( v, perm1, blend1 );
        v = sortStep( v, perm2, blend2 );
    }
    return v;
}

另外,如果需要输出向量的长度,请使用以下函数:

uint32_t vectorLength( __m128i v )
{
    uint32_t mask = (uint32_t)_mm_movemask_epi8( v );
    mask |= 0x10000;
    return _tzcnt_u32( mask );
}

C++相关问答推荐

找出文件是否包含给定的文件签名

MISRA C:2012 11.3违规强制转换(FLOAT*)到(uint32_t*)

DPDK-DumpCap不捕获端口上的传入数据包

C中的指针增量和减量(*--*++p)

在为hashmap创建加载器时,我的存储桶指向它自己

实现简单字典时C语言中的段错误

整型文字后缀在左移中的用途

在编写代码时,Clion比vscode有更多的问题指示器

在vfork()之后,链接器如何在不 destruct 父内存的情况下解析execve()?

当内存来自Malloc时,将char*转换为另一个指针类型是否违反了严格的别名规则?

C代码可以在在线编译器上运行,但不能在Leetcode上运行

CS50判断灯泡运动的问题,判断时多出一个灯泡,但不在终端上

基于蝶数恰好有8个除数的事实的代码

C程序printf在getchar while循环后不工作

如何不断地用C读取文件?

OpenGL 中的非渐变 colored颜色 变化

获取 struct 中匿名 struct 的大小

nullptr_t 是否会 destruct 类型双关或指针转换?

在 printf() 格式说明符中使用字段宽度变量

C 初学者 - struct 中的字符串不需要 Malloc