【问题标题】:Storing 16-bit integers in 32-bit registers在 32 位寄存器中存储 16 位整数
【发布时间】:2021-03-30 08:39:35
【问题描述】:

如果整数总是能够放入 16 位寄存器,那么使用 32 位寄存器存储等于或小于 16 位的整数会不会效率低?

mov eax, 180 ; Is this overkill?

另外,如果我将相同的整数存储在 16 位寄存器中,并且预计它会通过用户输入增长为大于 16 位的整数,我是否应该使用 16 位寄存器直到它需要 32-位寄存器还是最初从 32 位寄存器开始以节省以后移动到它?

【问题讨论】:

  • 使用16位寄存器实际上是8位、32位和64位寄存器中最差的
  • 在大数组上操作时只需要关心大小,因为这会影响单个高速缓存行或单个 SIMD 寄存器中可以容纳多少元素。在单个值中总是更喜欢原生 int 大小

标签: assembly x86-64


【解决方案1】:

如果整数总是能够放入 16 位寄存器,那么使用 32 位寄存器存储等于或小于 16 位的整数会不会效率低?

是的,这是对处理器资源的浪费,但无论如何它们都存在,而且在许多情况下很难使用剩余的寄存器位。例如,在 x86 上,32 位寄存器的第 1 位和第 2 位 8 位有一个寄存器名称/别名(例如 alah),但高 16 位没有寄存器名称或别名32 位寄存器的位(仅ax 用于低 16 位,eax 用于全部 32 位),因此当低位正在使用时,这些额外位很难有效地用于其他目的。 (还请考虑,我们通常根本不使用完整的寄存器集,因此这也可以被视为浪费——但这并不是值得特别关注的浪费。)

在其他架构上,例如MIPS、RISC V、ARM,甚至没有任何 8 位或 16 位寄存器的别名;它们都被假定为(至少)32 位宽。大多数操作都定义了如何设置完整的 32 位寄存器,并且从广义上讲,只有对内存的小型存储使用部分​​寄存器值。

@Arthur 对编码提出了一个很好的观点——它们实际上是针对 32 位机器上的 32 位数据类型优化的,并且要求仅 16 位操作有时需要额外的字节!这大部分只是 x86/x64 上的问题,因为其他处理器甚至根本无法指定 8 位或 16 位算术。 x64 处理器将 32 位寄存器上的操作定义为对 64 位寄存器的高 32 位也具有特定效果(例如归零),因此即使是 32 位操作也会更新整个寄存器——这节省了合并操作否则,在对寄存器进行 16 位或 8 位操作时,高位的先验值会延续而不是通过算术明确设置,否则这将是必要的。 (同样,这主要是一个 x86/x64 问题,因为其他处理器甚至不尝试让程序员可以使用更小的/更小的寄存器。)

我应该使用 16 位寄存器直到它需要一个 32 位寄存器,还是先从 32 位寄存器开始以节省以后移动到它

不,你会发现编写一个使用 16 位整数的程序非常困难,直到它使用更大的值然后切换到 32 位。您基本上必须多次编写代码并通过复杂的测试来决定何时切换。 (此外,如果你正在做加法,比如说,只有一个数字需要 32 位,但另一个仍然适合 16 位——我们可能会考虑引入相同代码的第三种或第四种情况。)

首先升级到 32 位更实用,只需编写一次代码并省略任何从一个到另一个的复杂切换。更容易编写,更容易测试(并且通过省略要使用的代码路径的测试,很可能会提高性能)。

顺便说一句,这适用于大多数语言,而不仅仅是汇编语言。

您还可以使用变长整数,广义上称为 BigInteger,它们会自动调整大小,但是对于可以使用处理器的自然大小/数据类型处理的较小数字,使用它们会产生显着的性能成本。

【讨论】:

  • 大多数 64 位 ISA 至少允许某些指令使用 32 位操作数大小,并对高位发生的情况有一些语义。 AArch64 与 x86-64 模型非常相似,w0..31(“字”)是 64 位 x0..31 的 32 位低半部分,写入 w 寄存器零扩展到 x 寄存器.没有像 x86 这样的部分寄存器恶作剧的 8 位和 16 位,除了 ARM32 的 NEON SIMD 版本,其中每个 q 寄存器别名一对 d 寄存器。
【解决方案2】:

我假设您在这里使用的是 32 位操作系统。 立即数中额外的 3 个字节不太可能导致停顿,但是使用 16 位寄存器的大小覆盖总是会花费一个周期,不用担心。

【讨论】:

  • 操作数大小的前缀在现代 x86 上不会“花费一个周期”(至少从 PPro 开始是英特尔)。除非它们导致 LCP 停顿,否则前端不会产生额外成本。 (LCP = 长度可变前缀,例如add cx, 1234 但不是add cx, 12,因为它只是带或不带前缀的 imm8,请参阅 Agner Fog 的 agner.org/optimize 微架构指南)。另一个成本是像mov ax, 1234 这样的只写目标,它必须合并到旧的 RAX 中,而不是在编写 EAX 时通过零扩展到完整的 reg 来启动新的依赖链。
  • Why doesn't GCC use partial registers? 解释了各种 CPU 如何处理它。 TL:DR:您建议每个寄存器只使用一个变量是正确的,但原因并不完全正确。
猜你喜欢
  • 1970-01-01
  • 2012-04-11
  • 1970-01-01
  • 2012-01-11
  • 2011-01-14
  • 2014-11-18
  • 1970-01-01
  • 2020-11-21
相关资源
最近更新 更多