【问题标题】:Is it faster to increase the pointer than to do "mov [pointer+1],eax"?增加指针比“mov [pointer+1],eax”快吗?
【发布时间】:2016-01-14 03:47:04
【问题描述】:

假设我们想在 EDI 中存储一个字符串。这样存储会不会更快

mov byte [edi],0
mov byte [edi+1],1
mov byte [edi+2],2
mov byte [edi+3],3
...

还是这样?

mov byte [edi],0
inc edi
mov byte [edi],1
inc edi
mov byte [edi],2
inc edi
mov byte [edi],3
inc edi
...

有些人可能会在 little-endian 中提出以下建议:

mov dword [edi],0x3210

或以下大端:

mov dword [edi],0x0123

但这不是我的问题的重点。我的问题是,增加指针然后执行 mov 从而需要更多指令是否更快,或者在每个 mov 指令中指定添加到 EDI 指向的偏移地址的数量是否更快?如果后者是正确的,那么在将多少个具有相同编号的 mov 指令添加到偏移地址之后,是否值得将那个数量添加到指针中?也就是说,这是

mov byte [edi+5],0xFF
mov byte [edi+5],0xFF
mov byte [edi+5],0xFF
mov byte [edi+5],0xFF

比这还快?

add edi,5
mov byte [edi],0xFF
mov byte [edi],0xFF
mov byte [edi],0xFF
mov byte [edi],0xFF

【问题讨论】:

  • 在不了解处理器品牌、架构等的情况下,这是一个毫无意义的问题。通常情况下,数据路径宽度的一次移动将比相同数据的字节移动更快。但即使这样也不是小菜一碟,因为现代架构优化了这种情况。
  • 请注意:它将是 0x00010203 而不是 0x0123。
  • 带有偏移量的指令经过高度优化,不会使流水线停滞,所以我想说它们会更快。但无论如何,您可能会受到内存速度的限制,即使您正在写入缓存。
  • @MarkRansom:如果存储序列的长度有限,那么它所占用的其他执行资源的数量决定了它与周围指令的混合程度。

标签: assembly x86


【解决方案1】:

有关如何优化 asm 的文档,请参阅 http://agner.org/optimize/ wiki 中的其他链接。


这边:

mov byte [edi],0
mov byte [edi+1],1
mov byte [edi+2],2
mov byte [edi+3],3
...

会更快。除了额外的一到四个字节的指令大小外,在任何当前的微架构 AFAIK 上使用置换都没有额外的成本。 Two-register addressing modes can be slower on Intel SnB-family CPUs,但固定位移没问题。

像 gcc 和 clang 这样的真正编译器在展开循环时总是使用第一种方法(有效地址中的位移)。


顺便说一句,0x03020100 的 4 字节存储几乎比四个单独的 1 字节存储快 4 倍。大多数现代 CPU 具有 128b 数据路径,因此任何高达 128b 的单个存储都占用与 8b 存储相同的执行资源。 AVX 256b 存储仍然比英特尔 SnB / IvB 上的两个 128b 存储便宜(如果对齐),而英特尔 Haswell 及更高版本可以在一次操作中完成一个 256b 存储。但是,mov-immediate to memory 仅适用于 8、16 和 32 位操作数。 mov r64, imm64(仅限注册)在 64 位模式下可用,但没有 128 或 256 条 mov-immediate 指令。


在 32 位模式下,inc reg 的单字节编码可用,inc edi / mov byte [edi],1 将具有相同的代码大小,但在最新的 Intel 和 AMD 微架构上仍能解码两倍的微指令。如果代码在存储吞吐量或其他方面仍然存在瓶颈,这可能不是问题,但没有比这更好的方法了。 CPU 非常复杂,通过计算 uops 进行的简单分析并不总是与您在实践中获得的结果相匹配,但我认为每个商店之间的inc 不太可能运行更快。你能说的最好的就是它的运行速度可能不会明显变慢。它可能会使用更多的功率/热量,并且对超线程不太友好。

在 64 位模式下,inc rdx 需要 3 个字节进行编码:1 个 REX 指定 64 位操作数大小(而不是默认的 32 位),1 个操作码字节指定 inc r/m,以及 1 个 mod/ rm 字节指定rdx 作为操作数。

所以在 64 位模式下,存在代码大小的缺点。在这两种情况下,inc 解决方案将使用高价值微指令缓存(在英特尔 SnB 系列 CPU 上)中的两倍多的条目,该缓存包含融合域微指令。

更多的微指令也意味着 ROB 中有更多的空间,所以乱序的 exec 看不到前面。

此外,inc 指令链将延迟存储地址微指令更早地计算多个存储地址(并将它们写入存储缓冲区)。英特尔 Ice Lake 有两个端口可以运行存储地址微指令(Haswell 中只有 3 个)。如果存储地址较早准备好,则以后加载会更好,因此 CPU 可以确定它们是独立的,或者它们确实重叠。它还会让它们更早地脱离调度程序 (RS),从而释放无序执行结构中的空间。


第二部分:

mov byte [edi+5],0xFF
mov byte [edi+5],0xFF
mov byte [edi+5],0xFF
mov byte [edi+5],0xFF

对比

add edi,5            ; 3 bytes to encode.
mov byte [edi],0xFF  ; saving one byte in each instruction
mov byte [edi],0xFF
mov byte [edi],0xFF
mov byte [edi],0xFF

除非代码大小非常重要(不太可能),或者有 许多 更多商店,请使用第一种形式。第二种形式长了一个字节,但少了一个融合域 uop。它将在拥有它们的 CPU 上的 uop-cache 中使用更少的空间。在较旧的 CPU(没有 uop 缓存)上,指令解码更多是瓶颈,因此在某些情况下,指令更好地排列成 4 组可能是瓶颈。不过,如果您在商店端口遇到瓶颈,情况就不会如此。

【讨论】:

  • 还请注意,如果您使用 no-inc 版本,则可以并行进行有效地址计算。在inc 版本中,每个有效地址计算都会停在前面的inc 上。 (或者至少在 Pentium 上是这样,这是我最后一次研究这种东西。)
  • @RaymondChen:是的,没错。我没有回答这个问题,因为他们是商店,所以如果他们需要很长时间才能最终退休并不重要。虽然现在我想了想,未解析的存储地址意味着所有后续加载都必须等待,以防存在“写后读”依赖关系。此外,能够执行存储地址微指令使它们脱离调度程序,这比重新排序缓冲区小得多。 (32 对 192 或其他东西)。
  • 如果你真的想深入挖掘,inc 指令很麻烦,因为它修改了 一些 标志,而其他的则保持不变。这意味着inc 之后的标志状态取决于多条指令的执行顺序,这进一步阻碍了并行性。
  • @Raymond:这只是 P4 上的软件的问题。 P6 和 SnB 微架构系列以及 AMD 都分别重命名了 EFLAGS 的不同部分,因此如果您读取最后一条指令未修改的标志以设置标志,则只会受到惩罚。 (例如,adc / dec / jnz 循环。)旧 CPU 的惩罚更严重(停顿与额外的 uop 合并)stackoverflow.com/questions/32084204/…。现代硬件尽可能地使用晶体管和功率来避免对标志的错误依赖,因此不妨节省 insn 字节。
猜你喜欢
  • 2019-03-30
  • 1970-01-01
  • 2012-12-10
  • 2012-10-10
  • 2017-05-05
  • 2016-05-29
  • 1970-01-01
  • 2013-09-10
  • 1970-01-01
相关资源
最近更新 更多