【问题标题】:Convention for displaying vector registers显示向量寄存器的约定
【发布时间】:2017-05-12 02:09:12
【问题描述】:

是否有显示/写入大型寄存器的约定,例如英特尔 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 中的任何内容的操作都独立于字节序)。字节序出现在寄存器 内存接口中,这里我询问的是寄存器中已经存在的数据。

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

伦敦证券交易所第一

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

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

MSE 优先

这里的主要优势似乎是较小元素的输出与较大尺寸的输出顺序相同(仅具有不同的分组)。例如,对于 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 当然,与 LSE-first 在 LE 平台上的相应优势相比,这种优势是微不足道的,因为 BE 在商品 SIMD 硬件中几乎已死。

【问题讨论】:

  • 我个人的看法是我更喜欢little-endian表示,但我不知道标准约定,这个问题似乎相当“基于意见”。我想许多调试器会使其成为一个可配置的选项,就像在显示字节大小的值、DWORD 大小的值、双精度值等之间切换的能力一样。
  • 我的经验法则是:匹配内存中的等效布局,所以如果内存中有0x1 0x2 0x3 ... 0xf,并且将其加载到向量寄存器,那么显示向量寄存器的内容也应该看起来像0x1 0x2 0x3 ... 0xf
  • @PaulR 我很确定你会得到0xf ... 0x3 0x2 0x1 的内存布局:D
  • @MargaretBloom:好吧,如果您使用某些编译器(例如Apple的gcc和clang)支持的%v格式扩展printf,那么这就是您得到的行为,我发现它很有帮助,因为您几乎可以忘记小端序的变幻莫测。
  • @CodyGray - 如果它是“什么是代表......的最佳方式”,那将纯粹是基于意见 - 但在这里我只是问是否存在现有约定,所以我可以遵循它:是/否问题,原则上可以根据现有事实回答。当然,对于需要多少现有行为才能将其声明为约定,或者应该由谁来定义约定,意见可能会有所不同——但当然,几乎所有问题都需要在这些方面进行一定程度的判断

标签: x86 sse simd avx


【解决方案1】:

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

考虑到选择,我更喜欢 cmets 中的 MSE 优先表示法,尤其是在设计带有 shuffle 或特别是打包/解包到不同元素大小的东西时。

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


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

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

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

    1234563空格、ctrl-左箭头、c、空格……或类似的东西。
  • 与 C 数组初始化程序的顺序相反。通常不是问题,因为_mm_set_epi* 使用 MSE 优先顺序。 (使用_mm_setr_epi* 匹配LSE-first cmets)。


在尝试设计 256b vpalignr 的车道交叉版本时,MSE 优先很好的一个示例:请参阅我对那个问题的回答 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 或混合时,MSE 可以轻松获得正确的 shuffle 常数,例如pshufd 当您可以将单词元素对保持在一起时,或者 pshufb 在整个向量中随机排列单词(因为 pshuflw/hw 是有限的)。

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

【讨论】:

    【解决方案2】:

    我的经验法则是:匹配内存中的等效布局,因此如果内存中有0x1 0x2 0x3 ... 0xf,并且将其加载到向量寄存器,那么显示向量寄存器的内容也应该看起来像0x1 0x2 0x3 ... 0xf .

    如果您使用某些编译器(例如 Apple 的 gcc 和 clang)支持的 printf%v 格式扩展,那么这就是您得到的行为,我发现它很有帮助,因为您几乎可以忘记小端的变幻莫测,例如

    #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;
    }
    

    使用合适的编译器会给出:

    v = 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf 0x10
    v = 0x201 0x403 0x605 0x807 0xa09 0xc0b 0xe0d 0x100f
    v = 0x4030201 0x8070605 0xc0b0a09 0x100f0e0d
    

    【讨论】:

    • 谢谢保罗。我实际上并不清楚“匹配内存中的布局”是什么意思。你的意思是你总是先打印最低有效字节,不管架构的字节序如何,等等一个大字节序架构你打印0xf 0xe ... 0x1,因为负载有相反的效果?或者你的意思是在一个 BE 架构上,相同的负载导致相反的顺序(即 0x1 现在在 MSB 中),你会颠倒输出顺序,所以它仍然显示为0x1 0x2 ...?我认为您的示例和描述可以双向解释。
    • 是的,我知道这很令人困惑,作为一个在 BE 和 LE SIMD 上工作多年的人,它仍然偶尔会绊倒我。我想我们只是在谈论如何显示/解释 SIMD 向量内容,根据您的问题,即您如何在调试器中显示寄存器或用于调试 printf 语句,甚至只是出于文档目的,是吗?在这种情况下,我会重复上面的答案,但也许可以通过说我将以与内存中元素相同的顺序表示向量 elements 来限定它,而不管字节顺序如何。所以上面的 C 例子...
    • ...说明了这一点(对于小端架构),在我看来,gcc/clang 中的 printf 扩展在显示矢量时会做正确的事情,就元素的顺序而言。
    • 嗯,我还不清楚。您需要在 LE 和 BE 架构上运行上面的示例,或者有关于 %v 如何工作的文档。 printf 的文档 I could find 没有解释它是如何工作的。您希望 %v 如何在 BE 拱门上显示上述代码?我相信它会显示0x10, 0xf, 0xe...,因为加载后寄存器中的数据将具有相反的顺序,据我所知,所有 printf 说明符都是字节序无关的。显示 1、2 是不寻常的。
    • 让我问一个更具体的问题。在 BE 架构上,忽略 printf 您的经验法则将如何打印出从内存 0x1 0x2 0x3 ... 加载的向量,如您的示例所示?我发现在这里介绍内存布局令人困惑。寄存器内容没有任何固有的字节序,因此您可以谈论它们,包括显示它们,通常不考虑字节序。所有操作通常在 LE 和 BE 中以预期的方式工作。因此,通过将矢量寄存器的显示与内存布局相关联,它将字节序引入其中,也许它不属于它。
    猜你喜欢
    • 2012-03-03
    • 2016-03-06
    • 2022-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-26
    • 2019-10-04
    • 2020-10-26
    相关资源
    最近更新 更多