gcc 提取__m128i中每个bool字节的低位?bool数组到压缩位图

tkclm6bt  于 2023-06-23  发布在  其他
关注(0)|答案(2)|浏览(167)

(编者注:这个问题原来是:如何访问__m128i对象的m128i_i8成员或一般成员?,尝试在GCC的__m128i定义上使用MSVC特定的方法。但这是一个XY问题,公认的答案是关于这里的XY问题。另一个答案 * 确实 * 回答了这个问题。)
我意识到微软建议不要直接访问这些对象的成员,但我需要设置它们,而documentation非常缺乏。
我继续得到错误“request for member 'm128i_i8' in '(my var name)',which is of non-class type 'wirelabel {aka __vector(2)long long int}'”,我不明白,因为我已经包含了所有正确的头,它确实识别__m128i变量。
注1:wirelabel是__m128i的typedef,即存在于标头中

typedef __m128i wirelabel

注2:使用注1的原因在以下其他问题中解释:tbb::cache_aligned_allocator: Getting "request for member...which is of non-class type" with __m128i. User error or bug?
注3:我使用的编译器是g++
注4:以下问题没有回答我的问题,但讨论了相关信息Why should you not access the __m128i fields directly?
我也知道有一个_mm_set_epi8函数,但它需要你一次设置所有8位部分,这不是我目前的选择。

接受答案回答的问题:

编辑:有人问我为什么我认为我需要访问__m128i对象的16个8位部分中的每一个,这是为什么:我有一个大小为'n*128'的bool数组(n是一个size_t),我需要将这些存储在大小为'n'的'wirelabel'数组中。
现在,因为wirelabel只是__m128i的别名/typedef(如果有区别请纠正我),128个布尔值的'n'个索引中的每一个都可以存储在'wirelabel'数组中。
然而,为了做到这一点,我认为需要将每8位转换为它的有符号等价物,并将其存储在数组中每个“无线标签”指针的正确8位索引中。

k75qkfdt

k75qkfdt1#

所以你的源数据是连续的?你应该使用_mm_load_si128,而不是乱用向量类型的标量分量。

真实的的问题是将一个bool数组(x86上g++使用的ABI中每个元素1个字节)打包成位图。您应该使用SIMD来执行 * 此操作,而不是使用标量代码来一次设置1位或字节。
pmovmskb_mm_movemask_epi8)非常适合提取输入的每个字节的一位。你只需要把你想要的比特安排到高比特。
显而易见的选择是移位,但是向量移位指令与Haswell上的pmovmskb竞争相同的执行端口(端口0)。(http://agner.org/optimize/)。相反,添加0x7F将为1的输入产生0x80(高位设置),但为0的输入产生0x7F(高位清除)。(x86-64 System V ABI中的bool必须以整数0或1的形式存储在内存中,而不是简单的0与任何非零值)。
为什么不使用pcmpeqb来对抗_mm_set1_epi8(1)?Skylake在端口0/1上运行pcmpeqb,但在所有3个向量ALU端口(0/1/5)上运行paddb。但是,在pcmpeqb/w/d/q的结果上使用pmovmskb是非常常见的。

#include <immintrin.h>
#include <stdint.h>

// n is the number of uint16_t dst elements
// We access n*16 bool elements from src.
void pack_bools(uint16_t *dst, const bool *src, size_t n)
{
     // you can later access dst with __m128i loads/stores

    __m128i carry_to_highbit = _mm_set1_epi8(0x7F);
    for (size_t i = 0 ; i < n ; i+=1) {
        __m128i boolvec = _mm_loadu_si128( (__m128i*)&src[i*16] );
        __m128i highbits = _mm_add_epi8(boolvec, carry_to_highbit);
        dst[i] = _mm_movemask_epi8(highbits);
    }
}

由于我们希望在写入此位图时使用标量存储,因此出于严格的别名原因,我们希望dst位于uint16_t中。对于AVX 2,您需要uint32_t。(或者,如果您使用combine = tmp1 << 16 | tmp来合并两个pmovmskb结果。但可能不要这样做)。
为了处理严格的锯齿问题,如果你想以后用不同的C类型访问你的掩码位图,你可以使用memcpy来存储,如another Q&A所示。
这将编译成一个asm循环,如下图所示(使用gcc7.3 -O3,在Godbolt编译器浏览器上)

.L3:
    movdqu  xmm0, XMMWORD PTR [rsi]
    add     rsi, 16
    add     rdi, 2
    paddb   xmm0, xmm1
    pmovmskb        eax, xmm0
    mov     WORD PTR [rdi-2], ax
    cmp     rdx, rsi
    jne     .L3

因此,这并不美妙(7个熔丝域uop->前端瓶颈,每1.75个时钟周期16个布尔值)。Clang展开2,并且每1.5个周期应管理16个布尔。
使用移位(pslld xmm0, 7)只能在Haswell上每2个周期运行一次迭代,在端口0上遇到瓶颈。这在Skylake和以后不是问题;移位可以在更多端口上运行**,因此_mm_slli_epi32(v, 7)在那里是好的**,并且避免了需要向量常量。另请参见提取__m128i中每个布尔字节的低位?bool数组到压缩位图

wko9yo5t

wko9yo5t2#

创建一个匿名联合,其中包含一个_m128i成员和一个要设置其成员的其他类型的数组。类型双关在C中是法律的的,并且在g++,clang++和MSVC中作为扩展支持。如果要设置单个位,可以将另一个成员声明为struct位字段。位字段的顺序是由实现定义的,但您使用的是Intel内部的,因此它将是little-endian。

相关问题