【问题标题】:How does CPU perform operation that manipulate data that's less than a word sizeCPU 如何执行操作小于字大小的数据的操作
【发布时间】:2019-10-19 12:44:18
【问题描述】:

我读过,当 CPU 从内存中读取时,它会一次读取内存的字大小(如 4 字节或 8 字节)。 CPU如何实现类似:

 mov     BYTE PTR [rbp-20], al

它只将一个字节的数据从 al 复制到堆栈。 (鉴于数据总线宽度为 64 位宽)如果有人能提供有关如何在硬件级别实现它的信息,那就太好了。

另外,众所周知,CPU在执行程序时,有程序计数器或指令指针指向下一条指令的地址,控制单元会将该指令取出到内存数据寄存器中,稍后再执行.比方说:

0:  b8 00 00 00 00          mov    eax,0x0

是 5 字节代码长(在 x84 上)并且

0:  31 c0                   xor    eax,eax

是 2 字节码长,它们有不同的长度。

如果控制单元想要获取这些指令,是否:

  1. 获取 8 个字节的字节码(可能包含多条指令),然后只执行其中的一部分。
  2. 获取小于 8 个字节的指令(仍然从内存中读取 8 个字节,但其他字节将被忽略)
  3. 指令已被填充(通过编译器或其他方式)。

像这样的指令呢:

0:  48 b8 5c 8f c2 f5 28    movabs rax,0x28f5c28f5c28f5c
7:  5c 8f 02

超过字长,CPU是如何处理的?

【问题讨论】:

标签: assembly x86 cpu cpu-architecture hardware-programming


【解决方案1】:

x86 根本就 是面向单词的架构。指令是可变长度的,没有对齐。

“字大小”在 x86 上不是一个有意义的术语;有些人可能会用它来指代寄存器宽度,但取指令/解码与整数寄存器无关。

在大多数现代 x86 CPU 的实践中,从 L1 指令缓存中提取指令发生在对齐的 16 字节或 32 字节提取块中。后面的流水线阶段找到指令边界并并行解码多达 5 条指令(例如 Skylake)。请参阅David Kanter's write-up of Haswell 的前端框图,显示从 L1i 缓存中获取 16 字节指令。

但请注意,现代 x86 CPU 也使用解码的 uop 缓存,因此它们不必处理难以解码的 x86 机器代码以处理非常频繁运行的代码(例如在循环内,甚至是大循环内) )。处理可变长度未对齐指令是旧 CPU 的一个重大瓶颈。


请参阅Can modern x86 hardware not store a single byte to memory?,了解有关缓存如何将存储吸收到正常内存区域(MTRR 和/或 PAT 设置为 WB = 回写内存类型)的更多信息。

在现代 Intel CPU 上,将存储从存储缓冲区提交到 L1 数据缓存的逻辑可以处理任何宽度的存储,只要它完全包含在一个 64 字节缓存行中即可。

更面向字的非 x86 CPU(如 ARM)通常使用缓存 (4 或 8 字节)的读取-修改-写入来处理窄存储。请参阅Are there any modern CPUs where a cached byte store is actually slower than a word store? 但是现代 x86 CPU 确实花费晶体管来使缓存字节存储或未对齐的更广泛的存储与对齐的 8 字节存储到缓存中一样有效。


假设数据总线宽度是 64 位宽

现代 x86 具有内置于 CPU 的内存控制器。 DDR[1234] SDRAM 总线有 64 条数据线,但单个读取或写入命令会启动 8 次传输突发,传输 64 个字节数据。 (并非巧合,64 字节是所有现有 x86 CPU 的高速缓存行大小。)

对于不可缓存的内存区域的存储(即,如果 CPU 配置为将该地址视为不可缓存,即使它由 DRAM 支持),可以使用告诉 DRAM 的the DQM byte-mask signals 进行单字节或其他窄存储从这个突发传输中实际存储 8 个字节中的哪一个。

(或者如果不支持(which may be the case),内存控制器可能必须读取旧内容并合并,然后存储整行。无论哪种方式,4 字节或 8 字节块 不 em> 这里的重要单位。DDR 突发传输可以缩短,但只能从 64 减少到 32 字节。我不认为 8 字节对齐写入实际上在 DRAM 级别非常特殊。它保证是“ atomic”在 x86 ISA 中,即使在不可缓存的 MMIO 区域上也是如此。)

存储到不可缓存的 MMIO 区域将产生适当大小的 PCIe 事务,最多 64 字节。


在 CPU 内核内部,数据缓存和执行单元之间的总线可以是 32 或 64 字节宽。 (或当前 AMD 上的 16 个字节)。 L1d 和 L2 缓存之间的缓存线传输也在 Haswell 及更高版本上通过 64 字节宽的总线完成。

【讨论】:

  • 根据this,部分DDR4芯片可能不支持写掩码。在这种情况下,目标 8 字节块被读取并与要写入的数据合并。
  • (更新:AMD Zen2 将 L1d 和执行单元之间的内部总线加宽到 32 字节)
【解决方案2】:

CPU 从不(或很少)与数据总线和内存通信——相反,数据总线在内存和缓存之间传输数据,而 CPU 与缓存通信。 CPU 的数据高速缓存接口可以写入高速缓存行中的单个字节或多个字节。所以用你的

mov     BYTE PTR [rbp-20], al

例如,要执行此操作,CPU 将首先确保包含该字节的行在数据缓存中(这可能涉及从内存中传输一个或多个总线大小的块),然后写入该字节。

解码指令来自指令缓存,该缓存经过优化以将数据流式传输到解码器中,因此它们可以处理跨字边界的未对齐指令。

【讨论】:

  • 当你提到缓存时,一切都开始变得有意义了,但是,由于对缓存缺乏了解,我无法清楚地看到谁处理,你有什么建议吗? / 进一步阅读内存缓存的内容?从我阅读的资料来看,他们将其简要描述为一个快速内存库,但从未涉及其他组件和缓存之间的通信。
  • @Sayakura:规范的文章是 Ulrich Drepper 的文章,What Every Programmer Should Know About Memory?
【解决方案3】:

如今,CPU 边缘的总线可能是 64 位的。但无论哪种方式 16、32、64 等。设计也可以/确实有所不同,但您要问的事情是读取的处理器会发出总线大小的读取,因此对于地址 0x1001,会发生读取 0x1000以某种形式(有时内存控制器或高速缓存控制器或该总线另一侧的任何东西都会将低位从地址中剥离出来)。

理想情况下,读取的下一层将执行字或总线大小的读取。您可能在这里有缓存,也可能没有缓存,这与这个问题无关,如果是这样,那么如果命中,那么该宽度将被读取并发送回 CPU,如果错过一些单位,通常会多次总线宽度将作为缓存线读取,字/或任何单元将被发送回 CPU。对于读取,CPU 通常将子总线字节数与该读取隔离并消耗它们而忽略其余部分。请注意,这不是浪费,而是相反。

写入是性能问题所在。如果您写入未对齐或肯定小于完整总线宽度,那么您需要向内存控制器指示来自无效位或字节通道的有效字节通道,通常是某种形式的字节通道。一种方法是使用字节掩码,因此对于 32 位总线,您将有 4 位字节掩码一个来表示一次通过该总线的 8 位字节中的每一个。然后内存控制器或缓存控制器将需要执行读取-修改-写入(有例外,但在这种情况下只需滚动)。因此,向 0x1001 写入一个字节将使 CPU 留在这个内部/关闭总线上,该地址或 0x1000 作为地址、0b0010 的字节掩码和 32 位数字形式的数据值,其中只有第二个字节车道具有有效位,其他位可能是垃圾或零或其他。

对于这类系统,询问这样的引用/问题是指以这些宽单元访问内存的外层,字节启用是可能的,但假设未使用。缓存本身很可能由宽 sram 组成,在这种情况下 32 位是正常的,因此要在缓存 sram 中写入单个字节位置,需要读取这 32 位,修改正在更改的 8 位,然后写sram位置。这与缓存直写或回写或任何完全不相关的内容完全无关。这是深埋在缓存中的 sram 的内部工作原理。用 8 位宽的存储器构建缓存会浪费芯片空间,还会增加信号的数量,导致一些浪费的空间来路由它们,再加上控制它们的逻辑,都被浪费了。因此,更宽的内存将用于稍微健全的设计。可能更像是 39 或 40 位宽,以便在这些 sram 上有一些 ecc。

如果您没有缓存或缓存未启用,则类似(如果不相同)。您可以从 arm 下载 axi 文档,您可以查找其他一些已知的总线。 x86 的内部工作原理虽然可以看到此活动,但在 Intel 或 AMD 之外确实没有记录任何业务。

x86 处理指令集的开销很大,您不应该看到这些写入对性能的影响。其他开销更少的架构,您可以/将会看到这些性能损失。

【讨论】:

    【解决方案4】:

    关于计算机体系结构的大多数书籍都讨论了缓存。在所问问题的级别上,Harris & Harris 的“数字设计和计算机架构”或在该级别上可能就足够了。

    您可能正在寻找我在下面附上的框图,以便快速了解管道并继续前进。我不知道有什么书可以做到这一点。我花了不到 30 分钟的时间来画这个(并且完全是为了好玩)——把它当作它的价值。但是,如果您发现错误或有其他更正,请在此处发布以供此页面的未来访问者使用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-01
      • 2018-07-05
      • 1970-01-01
      • 2015-07-15
      相关资源
      最近更新 更多