gpt4 book ai didi

c++ - 使用非x86架构上的非AVX指令移位xmm整数寄存器值

转载 作者:塔克拉玛干 更新时间:2023-11-03 07:07:45 25 4
gpt4 key购买 nike

我有以下问题,需要使用除AVX2以外的任何方法解决。

我在m128i变量中存储了3个值(不需要第4个值),并且需要将这些值移动4,3,5。我需要两个功能。一个用于这些值的右逻辑移位,另一个用于左逻辑移位。

有人知道使用SSE / AVX解决问题的方法吗?我唯一能找到的是_mm_srlv_epi32(),它是AVX2。

要添加更多信息。这是我尝试使用SSE / AVX优化的代码。这是我的绘图/检查引擎的一部分。

uint32_t Board::getMoversBlack(){
const uint32_t nocc=~(BP|WP);
const uint32_t BK = BP & K;
uint32_t movers = (nocc >> 4) & BP;
movers |= ((nocc & MASK_R3) >>3) & BP;
movers |= ((nocc & MASK_R5) >>5) & BP;
if (BK != 0) {
movers |= (nocc << 4) & BK;
movers |= ((nocc & MASK_L3) << 3) & BK;
movers |= ((nocc & MASK_L5) <<5) & BK;
}
return movers;
}

将不胜感激。

最佳答案

如果确实需要此功能(并且无法通过重新排列数据来避免这种情况),则可以完全/安全地模拟_mm_srlv_epi32,而不会破坏任何高位或低位。
对于编译时常量计数,您可以在大多数情况下混合使用左移和右移。
可能是错误的选择:

  • 解压成标量:糟糕。 Kinda对于编译时常量计数不利,但对于运行时变量计数则更糟,尤其是在您必须解压缩计数 vector 的情况下。不带BMI2 shrx的x86可变计数移位具有笨拙的语义,并在Intel SnB系列上解码为多个微指令。他们还接受额外的mov指令,以将移位计数放入cl中(如果尚未存在)。
  • 进行单独的移位,然后混合以从已移位该量的 vector 中获取元素。这不是很好,但是您可以在复制不需要的元素时将它们归零,从而降低混合成本。 (例如,如果已知高元素为零,则使用pshufd复制以从{0,22,0,0}的起始 vector 中获取{11,22,33, 0}的 vector ,然后重复{0,0,33,0}。)
    因此,将不使用的高位元素清零,将2x pshufd复制并混洗零位,将3x psrld计数不同,然后将未复制的 vector 中的其他元素剔除,然后将3个 vector 或在一起。 (如果您不保留 vector 的一个元素,这将需要更多的工作。)
    根据代码的其余部分和微体系结构,使用随机播放而不是MOVDQA + PAND可能不值得。如果任何元素使用相同的类次计数,此选项将更具吸引力。
    另外,您可以将lowt元素与movss混合成一个 vector ,并将下半部分与movsd混合。那些使用shuffle端口,因此shuffle吞吐量可能是个问题。这实际上可能非常可靠。

  • 希望有更好的选择。
  • Marc建议的SSE2版本(参见下文)在完全普通的情况下也适用。
  • 当最小移位计数和最大移位计数之间的差小于等于最小移位计数时,可以use @Marc's SSE4.1 suggestion 用作乘数作为变量左移,以解决右移计数的差异。或独自左移。对于大多数情况,这可能是最好的选择,即使vector-int乘法速度很慢,它也需要较少的指令。
  • __m128i srlv435_sse4(__m128i v)
    {
    __m128i rshift = _mm_srli_epi32(v, 3); // v >> 3
    // differences in shift count by multiplying by powers of 2
    __m128i vshift = _mm_mullo_epi32(rshift, _mm_setr_epi32(2,4,1,0)); // [ x >> 2, y >> 1, z >> 3, 0 ] Except with low bits truncated.
    __m128i shift2 = _mm_srli_epi32(vshift, 2); // [ x >> 4, y >> 3, z >> 5, 0 ]
    return shift2;
    }
    很好,因为它就地运行,即使没有AVX1,编译器也不需要任何MOVDQA指令来复制寄存器。
    请注意, SSE4.1 _mm_mullo_epi32并不快:Haswell上p0的2微秒:10c延迟和每2c吞吐量一个。 Skylake上的吞吐量更高,其中两个uops都可以在p0或p1上运行,但仍依赖10c延迟。 ( http://agner.org/optimize/标签Wiki中的其他链接。)
    这在Haswell之前具有更好的延迟,在此之前 pmulld是单uup指令(〜5个周期,1c吞吐量),而不是10个周期的2个相关uop。
    在AMD Bulldozer系列和Ryzen上,延迟= 4或5,吞吐量=每2c 1个。
    我没有检查 vector 转移的端口冲突。

    如果没有SSE4.1 ,则可以使用2x SSE2 _mm_mul_epu32一次进行2次乘法。为了排列奇数元素(1和3), pshufd将它们复制并拖曳到位置0和2, pmuludq在其中寻找它们。
    从偶数2个32位元素中产生2个64位结果,因此您无需进行预移位即可避免溢出。当移位计数之间的差大于最小移位时,这也意味着可以安全地使用它,因此SSE4.1方法无法将保留所有位数的元素保留在元素中。
    // general case: substitute in *any* shift counts and it still works.
    __m128i srlv_sse2(__m128i v) // [x y z w]
    {
    __m128i vs_even = _mm_mul_epu32(v, _mm_setr_epi32(1U<<1, 1U<<2, 1U<<0, 0)); // [ x<<1 z<<0 ] (64-bit elements)
    // The 4 (1U<<2) is unused, but this lets us share a constant with the SSE4 version, saving rodata size. (Compilers optimize duplicate constants for you; check the disassembly for same address)
    vs_even = _mm_srli_epi64(vs_even, 5); // [ x>>4 0 x>>5 0 ] (32-bit elements ready for blending with just an OR)

    __m128i odd = _mm_shuffle_epi32(v, _MM_SHUFFLE(3, 3, 1, 1));
    __m128i vs_odd = _mm_mul_epu32(v, _mm_setr_epi32(1U<<(32-3),0,0,0)); // [ (y<<32) >> 3 0 ] (64-bit elements)

    // If any elements need left shifts, you can't get them all the way out the top of the high half with a 32-bit power of 2.
    //vs_odd = _mm_slli_epi64(vs_odd, 32 - (3+2)); // [ garbage, y>>3, 0, 0 ]

    // SSE2 doesn't have blend instructions, do it manually.
    __m128i vs_oddhi = _mm_and_si128(vs_odd, _mm_setr_epi32(0, -1, 0, -1));
    __m128i shifted = _mm_or_si128(vs_even, vs_oddhi);

    return shifted;
    }
    这里有一些明显的优化:
    您的案例没有使用第4个元素,因此第2个乘法是没有意义的:只需移位并使用AND掩码即可清除高位元素。 vs_odd = _mm_srli_epi32v, 3);并将 0,-1,0,0用作AND掩码。
    而不是左移1和0,而是将x自身添加,并保持z不变。复制高64位归零的 vector 非常便宜( movq),但不比 movdqa便宜(在带消除功能的CPU上)。
        __m128i rshift = _mm_srli_epi32(v, 3);         // v >> 3
    __m128i xy00 = _mm_move_epi64(rshift);
    __m128i vshift = _mm_add_epi32(rshift, xy00); // [ x >> 2, y >> 2, z >> 3, 0 ]
    但这不能处理 y。我们可以将 y>>2vshift隔离开,然后再次添加它以生成 y>>1。 (但是请记住不要使用 y>>3中的旧 xy00)。
    我们也可以考虑使用 _mm_mul_epu32( pmuludq)一次,然后使用copy + shift + AND进行下一步(从原始 v而不是 rshift复制以缩短dep链)。这在您的情况下很有用,因为您没有使用top元素,因此只有一个有效的奇数元素,因此不需要可变移位。
    通过将 movqmovssmovsd组合使用,基本上将3个元素分别移位可能会带来更多好处。在端口压力,延迟,uop计数(前端吞吐量)和其他方面之间需要权衡取舍。例如我在想
    movdqa  xmm1, xmm0
    psrld xmm0, 3 # [ x>>3 y>>3 garbage ]
    psrld xmm1, 4 # [ x>>4 y>>4 garbage ]
    movss xmm1, xmm0 # [ x>>3 y>>4 garbage ] # FP shuffle

    psrld xmm0, 2 # [ garbage z>>5 ]
    movsd xmm0, xmm1 # [ x>>3 y>>4 z>>5 ] # FP shuffle
    例如,Haswell的每个时钟吞吐量只有1个移位,因此这并不妙。与乘法选项相比,它具有相当好的延迟。在Skylake上很好,其中2个端口可以运行 vector 即时移位。
    在Nehalem以外的Intel CPU上,FP在整数指令之间进行随机排序是可以的(在每种情况下,这都是2个周期的旁路延迟延迟损失,但吞吐量仍然可以)。我认为在AMD上也可以。
    当然,所有这些CPU都具有SSE4.1,因此,如果您使用动态运行库调度,则SSE2版本仅可在Core2 / K10上运行。 (而且我猜是较早的Atom或其他)。
    code + asm output on Godbolt

    关于c++ - 使用非x86架构上的非AVX指令移位xmm整数寄存器值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46993981/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com