显示矢量寄存器的惯例

时间:2016-12-27 19:30:32

标签: x86 sse simd avx

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

例如,如果在最低有效字节中有1,在最高有效字节中有20,在xmm寄存器中有其他地方为0,则对于逐字节显示,以下是首选(little-endian) ):

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

或者这是首选:

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

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

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

VS

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

讨论

我认为两个最有希望的答案是" LSE 1 first" (即,上面例子中的第一个输出)或" MSE first" (第二个输出)。这两者都不取决于平台的字节顺序,因为实际上一旦寄存器数据通常是字节独立的(就像GP寄存器上的操作或longint或C中的任何内容都与字节序无关) 。字节顺序出现在寄存器中< - >存储器接口,我在这里询问已经存在于寄存器中的数据。

可能存在其他答案,例如依赖于字节顺序的输出(而Paul R的答案可能是一个,但我无法告诉)。

LSE First

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

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

MSE First

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

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

基本上,即使从字节输出中,您也可以直接读取"单词或双字输出,反之亦然,因为字节已经是数字显示的通常MSB优先顺序。另一方面,LSE-first的相应输出是:

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

请注意,每个图层都会相对于其上方的行进行切换,因此读取较大或较小的值会更加困难。您需要更多地依赖输出对您的问题最自然的元素。

此格式还具有以下优点:在BE体系结构中,输出然后匹配存储在内存 3 中的相同向量的内存中表示。

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

1 最不重要的元素

2 这些编号不仅仅是出于文档目的 - 它们在架构上是可见的,例如,在随机掩码中。

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

2 个答案:

答案 0 :(得分:1)

我的经验法则是:匹配内存中的等效布局,所以如果你在内存中有0x1 0x2 0x3 ... 0xf,并将它加载到向量寄存器,那么显示向量寄存器的内容也应该像{ {1}}。

如果您使用某些编译器支持的0x1 0x2 0x3 ... 0xf %v格式扩展名(例如Apple的gcc和clang),那么这就是您获得的行为,我发现它很有帮助,因为你几乎可以忘记小字节序的变幻莫测,例如

printf

使用合适的编译器,可以得到:

#include <stdio.h>
#include <stdint.h>
#include <xmmintrin.h>

int main(void)
{
    uint8_t a[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };

    __m128i v = _mm_loadu_si128((__m128i *)a);

    printf("v = %#vx\n", v);
    printf("v = %#vhx\n", v);
    printf("v = %#vlx\n", v);

    return 0;
}

答案 1 :(得分:1)

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

鉴于选择,我更喜欢评论中的MSE优先表示法,尤其是在设计带有随机播放或特别是打包/解包到不同元素大小的内容时。

英特尔首先不仅在手册中的图表中使用MSE,而且还使用pslldq(字节移位)和psrlw(位移)等内在函数/指令的命名: a左位/字节移位朝向MSB 。 LSE优先思考并不能避免你在精神上扭转事物,这意味着你在考虑转移而不是加载/存储时必须这样做。由于x86是little-endian,你有时候不得不考虑这个。

在MSE中首先考虑向量,只需记住内存顺序是从右到左。当您需要考虑来自内存块的重叠未对齐加载时,可以按从右到左的顺序绘制内存内容,这样您就可以查看它的向量长度窗口。

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

MSE优先表示法的两个主要缺点是:

  • 更难向后键入字母(如{32}元素的AVX向量的h g f e | d c b a),所以我有时只是从右边开始并输入a,左箭头, b,空格,左旋箭头,c,空格,......或类似内容。

  • 与C array-initializer命令相反。通常不是问题,因为_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-first 的最坏情况,因为右移会从更远的左侧开出一个窗口。在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)。

当使用更大或更小的粒度混洗或混合时,MSE可以很容易地使shuffle正常。 pshufd当您可以将单词元素组合在一起时,或pshufb在整个向量中移动单词(因为pshuflw/hw是有限的)。

_MM_SHUFFLE(d,c,b,a)也按照MSE顺序排列。将其作为单个整数写入的任何其他方式也是如此,例如C ++ 14 0b11'10'01'000xE4(身份随机播放)。使用LSE优先表示法将使您的随机常量看起来相对于您的评论“向后”。 (pshufb常量除外,您可以使用_mm_setr

编写