YMM寄存器之间的逻辑转换

Logical shift between YMM registers

提问人:thequestioner 提问时间:10/31/2023 最后编辑:thequestioner 更新时间:11/2/2023 访问量:126

问:

我是否可以将一个 2048 位数字加载到 8 个 AVX ymm 寄存器中,并在所有这些寄存器之间左右移动位?

我一次只需要移动 1 位。

我试图在 AVX 上找到准确的信息,但很多时候 xmm/ymm/zmm 和进位之间的交互似乎不清楚。

程序集 x86-64 avx avx512

评论

1赞 Peter Cordes 10/31/2023
对于相关用例,如果移位计数是 32 位(4 字节)的倍数,则有 AVX-512 / (felixcloutier.com/x86/valignd:valignq),即与 XMM 一样的通道交叉 2 寄存器即时移位。valigndqpalignr

答:

4赞 harold 10/31/2023 #1

我试图在 AVX 上找到准确的信息,但很多时候 xmm/ymm/zmm 和进位之间的交互似乎不清楚。

这是简单的部分:没有互动。SSE/AVX 算术不涉及标志。有一些特定的指令可以比较/测试向量 () 或向量 ( 等) 中的标量,然后设置标志,但它们在这里并不那么有用。ptestcomiss

一种方法是从数字的顶部而不是底部开始,加载两个略微偏移(大部分重叠,因此其中一个向量与另一个元素相比被一个元素偏移)向量,并使用其中一个“连接和移位”指令(例如)进行左移,从前一个元素移位(通常它不是来自前一个元素, 它来自另一个向量,但这就是为什么我们在单元素偏移处加载第二个向量)而不是零。在 AVX2 中,您可以使用左移、右移和 来模拟这种情况。vpshldvpor

评论

0赞 thequestioner 10/31/2023
我想知道如果我在 ymm 中将 1 加到 0xff 并滚动到 0,会发生什么。进位是否记录在任何地方?
1赞 harold 10/31/2023
@thequestioner不行,你可以自己计算。此外,例如,如果您使用每个 dword 中的 31 位,则相应的进位会自动出现在每个单独总和的顶部,但这还不足以使多 dword 加法变得容易。无论如何,这里有一些技巧: numberworld.org/y-cruncher/internals/addition.html
1赞 Soonts 11/2/2023 #2

这是可能的,但不是直截了当的。

这是 C++ 中的 AVX2 实现,它在每个寄存器 5 条指令中执行此操作。

#include <immintrin.h>

// Shift AVX vector left by 1 bit
// The flag should contain either 0 or 1 in the lowest int32 lane, higher 96 bits are unused
inline __m256i shiftLeft1( const __m256i src, __m128i& carryFlag )
{
    // Shift 64 bit lanes right by 63 bits, i.e. isolate the high bit into low location
    __m256i right = _mm256_srli_epi64( src, 63 );
    // Cyclic permute across the complete vector
    right = _mm256_permute4x64_epi64( right, _MM_SHUFFLE( 2, 1, 0, 3 ) );

    // Deal with the carry flags
    const __m128i nextFlag = _mm256_castsi256_si128( right );
    right = _mm256_blend_epi32( right, _mm256_castsi128_si256( carryFlag ), 1 );
    carryFlag = nextFlag;

    // Shift 64 bit lanes left by 1 bit
    __m256i left = _mm256_slli_epi64( src, 1 );
    // Assemble the result
    return _mm256_or_si256( left, right );
}

// Shift AVX vector right by 1 bit
// The flag should contain either 0 or 0x80000000 in the highest int32 lane, lower 224 bits are unused
inline __m256i shiftRight1( const __m256i src, __m256i& carryFlag )
{
    // Shift 64 bit lanes left by 63 bits, i.e. isolate low bits into high location
    __m256i left = _mm256_slli_epi64( src, 63 );
    // Cyclic permute across the complete vector
    left = _mm256_permute4x64_epi64( left, _MM_SHUFFLE( 0, 3, 2, 1 ) );

    // Deal with the carry flags
    const __m256i nextFlag = left;
    left = _mm256_blend_epi32( left, carryFlag, 0b10000000 );
    carryFlag = nextFlag;

    // Shift 64 bit lanes right by 1 bit
    __m256i right = _mm256_srli_epi64( src, 1 );
    // Assemble the result
    return _mm256_or_si256( left, right );
}

这 5 条指令中的大多数都非常快,延迟为 1 个周期,但在大多数处理器上需要 3-6 个周期。幸运的是,该指令不依赖于进位标志,它只依赖于输入向量。现代无序处理器应该能够很好地运行该代码。vpermqvpermq

4 个向量中 1024 位数字的使用示例:

// 1024 bits of data in 4 AVX registers
struct Blob1k
{
    __m256i v0, v1, v2, v3;
};

void shiftLeft1( Blob1k& blob )
{
    __m128i cf = _mm_setzero_si128();
    blob.v0 = shiftLeft1( blob.v0, cf );
    blob.v1 = shiftLeft1( blob.v1, cf );
    blob.v2 = shiftLeft1( blob.v2, cf );
    blob.v3 = shiftLeft1( blob.v3, cf );
}

void shiftRight1( Blob1k& blob )
{
    __m256i cf = _mm256_setzero_si256();
    blob.v3 = shiftRight1( blob.v3, cf );
    blob.v2 = shiftRight1( blob.v2, cf );
    blob.v1 = shiftRight1( blob.v1, cf );
    blob.v0 = shiftRight1( blob.v0, cf );
}