繁体   English   中英

从 x86 中的寄存器加载值所需的时间

[英]Time taken to load value from registers in x86

我目前正在从Art of Intel x86 Assembly一书中学习 x86。 在讲述不同指令类和操作码如何工作的部分中,它说一字节操作码的编码方式类似于iiirrmmm ,其中三个i表示指令类, rr表示 4 个主要寄存器,而mmm可以是表示多个值的可选的 2 字节内存操作数,形式为AX[AX][AX+XXXX]等,例如101对应于[XXXX+BX]100对应于[BX]等。 开头也提到访问寄存器中的值所花费的时间为零时钟周期,因为它是在芯片上实现的。

然而,在解释一条指令完全工作所花费的时间时,为了计算出 CPU 计算内存操作数地址所花费的时间,它是这样说的:

在此处输入图片说明

会不会分别是 1 和 0 周期,因为书一开始就明确提到访问寄存器中的值是采用零时钟周期的? 为什么说访问BX的值需要 1 个周期?

明白这本书告诉你的关于代码执行速度的任何事情都是无稽之谈,这一点非常非常重要。 这本书很老了,15 年是很多狗生活在处理器开发中的。 即使从您的屏幕截图中看到的内容在当时已经不再真实,但今天变得危险地不真实。

接下来,CPU 计算内存操作数的地址

不,不是真的。 操作数地址计算是 AGU(“地址生成单元”)的工作。 处理器内核上的独立电路,独立于主执行内核运行。 这就是为什么做额外的工作可能需要 0 个 cpu 周期,工作是并发完成的。 这不仅限于 AGU,现代处理器具有许多可以同时完成作业的执行单元。

我们假设时钟周期和内存周期是等价的

那时不是真的,今天是非常不真实的。 内存总线比处理器内核慢数百倍。 一个与距离有关的问题,电信号必须传播得越,就越难将其传送到目的地而不会被破坏。 只有慢下来才能解决这个问题。 具有千兆赫时钟频率的现代处理器在高速缓存、在 RAM 中存储数据副本的额外内存方面投入了大量资金。 L1 缓存非常重要,它可以存储 32 KB 的数据和 32 KB 的指令,并且离处理器内核最近。 仍然需要 3 个 cpu 周期才能读取。 L2 和 L3 更大,不可避免地会离得更远,因此需要更多的周期。 任何因为从 RAM 读取数据需要 150 个 cpu 周期而遭受执行停顿的程序当然将是一个性能非常差的程序,无论它使用什么指令。

这不是不适停止的地方,这本书的整个前提今天非常具有误导性。 现代处理器实际上并不执行 x86 指令。 它们具有相当于 Java 或 .NET 中使用的那种即时编译器。 他们将 x86 指令翻译成“微操作”,CISC 指令被翻译成 RISC 指令。 易于跨多个执行子单元乱序和并发执行的那种。 究竟这看起来像什么是一个非常保守的秘密,像英特尔和 AMD 这样的公司将其视为知识产权,没有人应该知道任何事情。 最重要的是,没有人应该依赖它,因为这会使他们难以改进处理器设计。

这项创新的一个明显牺牲品是,谈论一条指令占用一定数量的 CPU 周期不再有意义。 我已经向您指出了 Agner Fog 的手册。 它谈到了延迟,即解码指令并获得结果所需的时间。 吞吐量,受可以同时执行的相同指令数量的影响。 这些数字只是给你一个处理器需要多努力工作的暗示,它们对于预测程序的实际执行时间完全没有用。 添加缓存的状态、内存总线的速度、预取器猜测需要提前检索的内存位置的能力,以及分支预测器在猜测代码流时作为强随机器的运气量. 只有分析器可以告诉您花费了多长时间。

其他人指出,您正在阅读的这本书已经很旧了,因此它告诉您的有关指令时间的内容在今天并不真正相关。 关于“内存周期”和“时钟周期”等价的那一点尤其可以追溯到 80 年代初。

一条指令需要多少个时钟周期来执行取决于指令触发的数据依赖性,以及 CPU 指令集设计者专注于优化指令解码和执行的机器数量。

较旧的机器会使用许多时钟来获取和解码复杂的指令,然后通常使用几个时钟来访问内存(在 80 年代后期,时钟频率为 10 Mhz)。 多时钟解码是由复杂的指令集设计、缺乏大量资源(空间和晶体管)用于指令解码以及通过更大的硅几何结构造成的长时间延迟造成的。 存储器的访问时间为 70 ns,因此只需要几个时钟(例如 10 个),因为时钟周期要慢得多。

在实现上比现代 CPU 简单得多,CPU 内部通常有一个简单的有限状态机来控制指令执行。 通过大致了解这个 FSA 是如何工作的,你可以预测指令解码时间,包括解码内存寻址的时间,正如你的书所建议的那样。 不再是真的。

现代机器具有非常高的时钟频率:2-4 Ghz。 这就像快 1000 倍。 这是可能的,因为晶体管要小得多,因此电流通过它们所需的时间更少。 此外,由于有如此多的晶体管,设计人员可以在解码/执行和缓存方面投入大量额外的硅。 奇怪的是,内存并没有变得更快,因此以时钟为单位访问内存所需的相对时间出人意料地增加了。 40 ns 内存需要 160 个 4Ghz 时钟。

可以说,芯片可以解码一条指令,并将执行该指令所需的所有信息存储在缓存中(现代英特尔 CPU 对复杂指令做了很多工作)。 这意味着一条复杂的指令在第一次遇到时可能需要几十个周期来解码,而在再次遇到时需要一个时钟(“在解码后的指令缓存中查找”),这种情况经常发生(考虑循环)。 好消息是平均指令解码时间非常短; 坏消息是,您无法准确估计指令解码和运行所需的时间,因为这取决于所涉及的优化和缓存的数量、特定指令执行的缓存是否被命中以及其他指令由于数据依赖性,仍然被处理并具有资源访问的优先级。

实际上,晶体管的供应量仍然(巨大但)有限,因此指令解码器无法缓存所有内容。 设计师们仍在权衡利弊。 一个关键的权衡是“简单”(RISC)指令(往往被执行很多次)分配了大量资源,使它们能够在每次遇到时快速解码,而复杂指令(往往执行频率较低)得到的资源更少硬件资源扔给他们。

具体到解码一个寄存器访问需要多长时间的问题:很明显,访问一个寄存器,首先需要你知道访问哪个寄存器,即使它是在芯片上的。 所以在读完指令后,需要一定的时间来提取寄存器字段。 如果您认为芯片上的最短时间是用时钟来衡量的,那么获得该字段需要的时间超过零,最多至少需要 1 个时钟。 真正激进的硬件可能会在单个时钟周期内执行多项操作,因此实际上可以设计对寄存器进行解码的 CPU,并且获取指定寄存器的时间少于一个时钟。 一些现代英特尔处理器在一个步骤中解码成对指令,例如“比较;jmp 条件”。

从您的角度来看,这意味着指令平均执行速度相当快。 伤害的是对非缓存位置的内存访问。

我的 CPU 一阶模型是它们无限快,你所要做的就是担心内存访问。 这意味着我倾向于用寄存器中的计算(速度很快)来换取内存访问时间。 压缩你的数据结构,它不会伤害:-}

寄存器操作数的零周期意味着仅寄存器操作的延迟是操作的基线(寄存器与 ALU 有快速连接)。 当使用内存操作数时,内存访问延迟会被添加到操作延迟中。
部分内存访问延迟是地址计算。 包含地址的寄存器(或它的一部分,在复杂的寻址模式中)必须路由到 CPU 的寻址单元而不是它的 ALU。
然后使用该地址访问内存,此时 CPU 中的路由是采用零还是一个周期的问题变得荒谬:内存延迟可能会大几个数量级。
底线:没有人关心那个周期。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM