x86 - 显示向量寄存器的约定

标签 x86 sse simd avx

是否有显示/写入大型寄存器的约定,例如英特尔 AVX 指令集中可用的那些?

例如,如果最低有效字节为 1,最高有效字节为 20,xmm 中的其他位置为 0。寄存器,对于按字节显示是以下首选(小端):

[1, 0, 0, 0, ..., 0, 20]

或者这是首选:
[20, 0, 0, 0, ..., 0, 1]

同样,当显示由较大数据项组成的寄存器时,是否应用相同的规则?例如,要将寄存器显示为 DWORD,我假设每个 DWORD 仍然以通常的(大端)方式写入,但 DWORD 的顺序是什么:
[0x1, 0x0, ..., 0x14]

对比
[0x14, 0x0, ..., 0x1]

讨论

我认为两个最有希望的答案只是“LSE1 first”(即上面示例中的第一个输出)或“MSE first”(第二个输出)。两者都不依赖于平台的字节序,因为实际上在寄存器中的数据通常是字节序独立的(就像对 GP 寄存器或 longint 或 C 中的任何内容的操作都独立于字节序)。字节序出现在寄存器 <-> 内存接口(interface)中,这里我询问的是寄存器中已经存在的数据。

可能存在其他答案,例如取决于字节序的输出(Paul R 的答案可能是一个,但我不知道)。

伦敦证交所第一

LSE-first 的一个优势似乎特别是字节输出:通常字节从 0 到 N 编号,LSB 为零2,因此 LSB-first 输出输出它的索引增加,就像你输出一个大小为 N 的字节数组。

它在小端架构上也很好,因为输出然后匹配存储到内存中的同一向量的内存中表示。

MSE优先

这里的主要优势似乎是较小元素的输出与较大尺寸的输出顺序相同(仅具有不同的分组)。例如,对于 MSB 表示法中的 4 字节向量 [0x4, 0x3, 0x2, 0x1] ,字节元素、字和双字元素的输出将是:

[0x4, 0x3, 0x2, 0x1]
[0x0403,0x0201]
[0x04030201]

从本质上讲,即使从字节输出中,您也可以“读取”字或双字输出,反之亦然,因为字节已经处于通常的 MSB 优先顺序以进行数字显示。另一方面,LSE-first 的相应输出是:

[0x1, 0x2, 0x3, 0x4]
[ 0x0201 , 0x0403 ]
[0x04030201]

请注意,每一层都相对于它上面的行进行交换,因此读取更大或更小的值要困难得多。您需要更多地依赖输出对您的问题最自然的元素。

这种格式还有一个优点,即在 BE 架构上,输出与存储到 memory3 的同一向量的内存表示相匹配。

英特尔在其手册中首先使用 MSE。

1 最不重要的元素

2 这样的编号不仅用于文档目的 - 它们在架构上是可见的,例如,在洗牌掩码中。

3 当然,与 LSE-first 在 LE 平台上的相应优势相比,这种优势是微不足道的,因为 BE 在商品 SIMD 硬件中几乎已死。

最佳答案

保持一致是最重要的;如果我正在处理已经具有 LSE 优先注释或变量名称的现有代码,我会匹配它。

如果可以选择,我更喜欢评论中的 MSE 优先表示法 ,尤其是在设计带有混洗或特别是打包/解包到不同元素大小的东西时。

英特尔不仅在手册中的图表中使用 MSE 优先,而且在内部函数/指令的命名中,如 pslldq (字节移位)和psrlw (位移):向 MSB 左移位/字节.伦敦政治经济学院优先的思维并不能让你在心理上扭转事情,这意味着你必须在考虑轮类而不是加载/存储时这样做。由于 x86 是 little-endian,因此您有时不得不考虑这一点。

在 MSE 优先考虑向量时,只需记住内存顺序是从右到左。当您需要考虑从一 block 内存中重叠未对齐的负载时,您可以按从右到左的顺序绘制内存内容 ,因此您可以查看它的矢量长度窗口。

在文本编辑器中,在某些内容的左侧添加新文本并将现有文本移到右侧是没有问题的,因此向评论添加更多元素不是问题。

MSE-first 表示法的两个主要缺点是:

  • 很难向后键入字母(例如 h g f e | d c b a 用于 32 位元素的 AVX 向量),所以我有时只是从右侧开始并键入 a ,左箭头,b , 空格, ctrl-左箭头, c ,空间,...或类似的东西。
  • 与 C 数组初始化器顺序相反。通常没有问题,因为 _mm_set_epi*使用 MSE 优先顺序。 (使用 _mm_setr_epi* 匹配 LSE 优先评论)。


  • 一个 MSE-first 很好的例子是在尝试设计 256b vpalignr 的车道交叉版本时。 : 看我对那个问题的回答
    How to concatenate two vector efficiently using AVX2? .这包括 MSE 优先表示法中的设计说明。

    作为另一个示例,考虑在整个向量中实现可变计数字节移位。你可以制作一张 pshufb 的表格控制向量,但这将是对缓存占用的巨大浪费。从内存中加载滑动窗口要好得多:

    /*  Example of using MSE notation for memory as well as vectors
    
    // 4-element vectors to keep the design notes compact
    // I started by just writing down a couple rows of this, then noticing which way they lined up
    << 3:                       00 FF FF FF
    << 1:                 02 01 00 FF
       0:              03 02 01 00
    >> 2:        FF FF 03 02
    >> 3:     FF FF FF 03
    >> 4:  FF FF FF FF
    
           FF FF FF FF 03 02 01 00 FF FF FF FF
      highest address                       lowest address
    */
    
    #include <immintrin.h>
    #include <stdint.h>
    // positive counts are right shifts, negative counts are left
    // a left-only or right-only implementation would only have one side of the table,
    // and only need 32B alignment for the constant in memory to prevent cache-line splits.
    __m128i vshift(__m128i v, intptr_t bytes_right)
    {   // intptr_t means the caller has to sign-extend it to the width of a pointer, saving a movsx in the non-inline version
    
       // C11 uses _Alignas, C++11 uses alignas
        _Alignas(64) static const int32_t shuffles[] = { 
            -1, -1, -1, -1,
            0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c,
            -1, -1, -1, -1
        };  // compact but messy with a mix of ordering :/
        const char *identity_shuffle = 16 + (const char*)shuffles;  // points to the middle 16B
    
        //  count &= 0xf;  tricky to efficiently limit the count while still allowing >>16 to zero the vector, and to allow negative.
        __m128i control = _mm_load_si128((const __m128i*) (identity_shuffle + bytes_right));
        return _mm_shuffle_epi8(v, control);
    }
    

    这是 MSE 优先的最坏情况 ,因为右移会从更左的位置打开一个窗口。在 LSE 优先表示法中,它可能看起来更自然。尽管如此,除非我得到了一些倒退的东西:P,我认为它表明你可以成功地使用 MSE 优先表示法,即使是你认为很棘手的事情。它并没有让人费解或过于复杂。我刚开始写下随机播放控制向量,然后将它们排成一行。如果我使用 uint8_t shuffles[] = { 0xff, 0xff, ..., 0, 1, 2, ..., 0xff };,我可以在转换为 C 数组时稍微简单一些。 .
    这个我没测试过,只有that it compiles to one instruction :
        vpshufb xmm0, xmm0, xmmword ptr [rdi + vshift.shuffles+16]
        ret
    

    当您可以使用位移而不是随机播放指令时,MSE 让您更容易注意到,以减少端口 5 上的压力。 psllq xmm, 16/_mm_slli_epi64(v,16)将字元素左移一位(在 qword 边界处归零)。或者当您需要移位字节元素,但唯一可用的移位是 16 位或更宽时。最窄的每元素可变移位是 32 位元素 ( vpsllvd )。

    当使用更大或更小的粒度 shuffle 或 blends 时,MSE 可以轻松获得正确的 shuffle 常数,例如pshufd当您可以将成对的单词元素放在一起时,或pshufb在整个向量中打乱单词(因为 pshuflw/hw 是有限的)。
    _MM_SHUFFLE(d,c,b,a)也按 MSE 顺序排列。将它写成单个整数的任何其他方式也是如此,例如 C++14 0b11'10'01'000xE4 (身份洗牌)。使用 LSE 优先表示法将使您的 shuffle 常量相对于您的评论看起来“向后”。 (除了 pshufb 常量,你可以用 _mm_setr 编写)

    关于x86 - 显示向量寄存器的约定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41351087/

    相关文章:

    performance - X86 Broadwell 上的吞吐量 FMA 和乘法

    c - _mm_set1_ps 和 _mm_set_ps1 有什么区别?

    c++ - 对于具有所有相同组件的 SSE vector ,是动态生成还是预先计算?

    c++ - 重叠数组、自动矢量化和限制的总和

    c - SIMD 值得吗?有更好的选择吗?

    c - 如何将 AVX/SIMD 与嵌套循环和 += 格式一起使用?

    assembly - 运行用 NASM 编写的 Win32 应用程序导致 'This app cant run on your pc' 错误

    c - 使用 SSE 在 __m128i vector 中获取最大值?

    x86 - 什么特别地将 x86 缓存行标记为脏 - 任何写入,还是需要显式更改?

    c++ - 如何编写可移植的 simd 代码以实现复杂的乘法归约