【问题标题】:Why does shift right in practice shifts left (and viceversa) in Neon and SSE?为什么在 Neon 和 SSE 中实际上向右移动会向左移动(反之亦然)?
【发布时间】:2015-05-26 08:52:08
【问题描述】:

(注意,在 Neon 中,我使用 this data type 来避免处理 16 位数据类型之间的转换)

为什么在实践中内在函数中的“左移”是“右移”?

// Values contained in a
// 141 138 145 147 144 140 147 153 154 147 149 146 155 152 147 152
b = vshlq_n_u32(a,8);
// Values contained in b
// 0 141 138 145 0 144 140 147 0 154 147 149 0 155 152 147
b = vshrq_n_u32(a,8);
// Values contained in b
// 138 145 147 0 140 147 153 0 147 149 146 0 152 147 152 0

我记得在使用_mm_slli_si128 时发现了同样的情况(虽然不同,换班后的结果如下:

// b = _mm_slli_si128(a,1);
// 0 141 138 145 147 144 140 147 153 154 147 149 146 155 152 147

是因为字节序吗? 会因平台而异吗?

【问题讨论】:

  • 在 C++ 中,联合不能绕过转换。
  • @BenVoigt 也许我以错误的方式使用了“绕过”一词。如果我使用链接中描述的数据类型,我可以将其用作vshlq_n_u32vget_low_u8vuzp_u8 等函数的输入和输出...
  • 也许你可以把你的手写顺序改成小端:把低地址词写在右手边,这样会更明显。
  • 是的,这是字节序。如果将其打印为四个无符号 32 位值的集合,您将看到指令将它们乘以 256,并丢弃高字节(感谢更正)。
  • @user3528438 问题在于,实际上我正在处理像素(无符号字符值),并且它们按该顺序存储。

标签: c++ sse shift neon intrinsics


【解决方案1】:

这些内在函数的结果似乎取决于系统字节序,因此如果我们将代码移植到大字节序系统,我已经准备好提出一个标志

#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
    #pragma GCC error "Intrinsics used with little endian systems in mind. Start by reviewing all shifts operators."
#endif

checking endianness at compile time

【讨论】:

  • 字节序不会影响移位指令:右移总是从 MSB 移到 LSB,左移总是从 LSB 移到 MSB。然而,字节序确实会影响您将数据加载到寄存器中的方式:在大端模式下,向量加载将低地址字节放入寄存器中的 LSB,而在小端模式下,向量加载将低地址字节放入向量寄存器中的 LSB。
  • @PaulR 我不明白这有什么关系。如果我从内存中加载 16 个字节(例如灰度值)141 138 145 147 144 140 147 153 154 147 149 146 155 152 147 152,而不是将它们右移vshrq_n_u32,小端将给出138 145 147 0 140 147 153 0 147 149 146 0 152 147 152 0,而大端将给出0 141 138 145 0 144 140 147 0 154 147 149 0 155 152 147。我错过了什么?
  • @PaulR 您没有提供141152 是否是内存中的低地址端。
  • @user3528438 这就是我的 16 次咬合存储在内存中的顺序,也就是打印像 stackoverflow.com/questions/13257166/print-a-m128i-variable/… 这样的变量时得到的顺序。低位地址是存储 141 的地方。我觉得有一些基本的东西我没有得到......
  • 然后在小端机器上,如果141 获取地址,则从那里加载一个向量,然后141 将在寄存器中显示为最低字节,即最右边的字节。如果你在大端机器上做同样的事情,141 将出现在最左边的字节中。这就是我说手写顺序的原因:当我写或打印一些小端字节流时,我总是将低地址字放在高地址字的右边。
【解决方案2】:

您说“这是因为字节序”,但这更像是类型滥用的情况。您正在假设跨字节/字边界的机器的位顺序以及对操作施加局部字节序的非字节指令(您使用的是 _u32 指令,该指令期望值是无符号 32 位值,而不是数组8 位值)。

正如您所说,您要求它通过 /asking/ 以 32 位单位移动值来移动一系列无符号字符值。

不幸的是,如果您希望能够对它们进行架构转换,则需要将它们按架构顺序排列。

否则,您可能想要查找 blit 或移动指令,但您无法在不支付架构成本的情况下人为地将机器类型强制转换为机器寄存器。字节序只是您的头疼之一(对齐、填充等)

--- 后期编辑 ---

从根本上说,您混淆了字节和位移,我们认为最高有效位是“左”

bit number
87654321

hex
8421
00008421

00000001  = 0x01 (small, less significant)
10000000  = 0x80 (large, more significant)

但是您要移位的值是 32 位字,在小端机器上,这意味着每个后续地址都会增加值的一个更重要的字节,对于 32 位字:

bit numbers
                1111111111111111
87654321fedcba0987654321fedcba09

表示32位值0x0001

                1111111111111111
87654321fedcba0987654321fedcba09

00000001000000000000000000000000

向左移动 2 个位置

00000001000000000000000000000000
     v<
00000100000000000000000000000000

要将其左移 8 个位置,我们必须将其变形到下一个地址:

00000100000000000000000000000000
      >>>>>>>v
00000000000001000000000000000000

如果您以字节为单位,这看起来像是右移。但是我们告诉这个 little-endian CPU 我们正在处理 uint32,所以这意味着:

                1111111111111111
87654321fedcba0987654321fedcba09
 word01  word02  word03  word04   
00000001000000000000000000000000 = 0x0001
00000100000000000000000000000000 = 0x0004
00000000000001000000000000000000 = 0x0400

问题在于,这与您对 8 位值的本地数组所期望的顺序不同,但您告诉 CPU 值是 _u32,因此它使用它的本机字节序进行操作。

【讨论】:

  • 你说得有道理,但我不明白为什么在使用内在 _mm_slli_si128 时不能做出这样的滥用......它基本上是为了滥用而设计的!我的转储代码在哪方面是错误的?它以与我从内存中加载它们时相同的顺序打印咬痕......
  • 不完全是,它把它当作 128 位,单 sse 寄存器,值,所以它仍然是一样的需要它只是它的 4 个本地词:)
  • “neediness”是“endianness”。它在我的 Kindle 字典里,但它似乎无法抗拒改变它。
  • 再一次,“你的转储代码对于这个架构是错误的”是什么意思?
  • 脸颊有点笨拙,我会用扩展的解释进行编辑。
猜你喜欢
  • 1970-01-01
  • 2023-03-04
  • 2013-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多