我有一个关于组装的基本问题。
如果寄存器也可以在内存上工作,为什么我们还要费心只在寄存器上进行算术运算呢?
例如,以下两个原因(本质上)将相同的值计算为答案:
代码段1
.data
var dd 00000400h
.code
Start:
add var,0000000Bh
mov eax,var
;breakpoint: var = 00000B04
End Start代码段2
.code
Start:
mov eax,00000400h
add eax,0000000bh
;breakpoint: eax = 0000040B
End Start据我所知,大多数文本和教程主要是在寄存器上进行算术运算。使用寄存器只是更快吗?
编辑:太快了:)
给出了几个很好的答案;根据第一个好答案选择了最好的答案。
发布于 2010-03-02 14:15:56
如果你查看计算机体系结构,你会发现一系列的内存级别。那些靠近CPU的是快速,昂贵(每一位),因此小,而在另一端你有大的,慢的和便宜的存储设备。在现代计算机中,这些通常类似于:
CPU registers (slightly complicated, but in the order of 1KB per a core - there
are different types of registers. You might have 16 64 bit
general purpose registers plus a bunch of registers for special
purposes)
L1 cache (64KB per core)
L2 cache (256KB per core)
L3 cache (8MB)
Main memory (8GB)
HDD (1TB)
The internet (big)随着时间的推移,添加了越来越多的缓存级别--我记得有一段时间CPU没有任何板载缓存,我甚至还不算老!如今,硬盘配备了板载缓存,互联网缓存在任意数量的地方:在内存中,在硬盘上,也可能在缓存代理服务器上。
有一个戏剧性的(通常是几个数量级的)下降带宽并增加延迟
在远离CPU的每一步中。例如,硬盘可能能够以100MB/s的速度读取,延迟为5ms (这些数字可能不完全正确),而您的主内存可以以6.4 at /s的速度读取,延迟为9 9ns (六个数量级!)。延迟是一个非常重要的因素,因为您不希望让CPU等待的时间超过它必须等待的时间(对于具有深度管道的体系结构尤其如此,但这将另行讨论)。
这个想法是,您通常会一次又一次地重用相同的数据,因此将其放在一个小的快速缓存中以供后续操作是有意义的。这被称为
时间局部性。位置的另一个重要原则是空间局部性
也就是说,彼此靠近的内存位置可能会在大约同一时间被读取。正是由于这个原因,从RAM读取将导致读取更大的RAM块并将其放入CPU上的高速缓存中。如果不是因为这些局部性原则,那么内存中的任何位置都有同样可能在任何时候被读取,因此无法预测下一步将访问什么,并且世界上所有级别的缓存都不会提高速度。您也可以只使用硬盘驱动器,但我相信您知道分页时计算机突然停止工作是什么感觉(这基本上是使用HDD作为RAM的扩展)。从概念上讲,除了硬盘以外没有内存是可能的(许多小型设备只有一个内存),但与我们熟悉的相比,这将是非常缓慢的。
拥有寄存器(并且只有少量的寄存器)的另一个优点是,它让您拥有
更短的指令。如果你有包含两个(或更多) 64位地址的指令,你将会有一些很长的指令!
发布于 2017-08-10 11:11:07
就像你学习汇编的其他“普通”x86一样,它是一个寄存器machine1。还有其他方法可以设计一些你可以编程的东西(例如,图灵机沿着内存中的逻辑“磁带”移动,或者生命的游戏),但寄存器机器已经被证明基本上是获得高性能的唯一途径。
https://www.realworldtech.com/architecture-basics/2/
涵盖可能的替代方案,如累加器或堆叠机,它们现在也已过时。尽管它省略了像x86这样的CISCs,它既可以是加载存储,也可以是寄存器内存。x86指令can actually bereg,mem;reg,reg;甚至mem,reg。(或直接提供消息来源。)
脚注1:抽象的计算模型称为register machine不区分寄存器和内存;它所称的寄存器更像是真实计算机中的内存。我在这里说“注册机器”是指具有
多通用寄存器,而不是只有一个累加器,或者堆栈机或其他任何东西。大多数x86指令都有2个显式操作数(but it varies),其中最多一个可以是内存。即使像6502这样的微控制器只能在一个累加器寄存器中进行数学运算,也几乎总是会有一些其他的寄存器(例如指针或索引),而不像Marie或LMC这样的真正的玩具ISA,它们的编程效率非常低,因为你需要不断地将不同的东西存储并重新加载到累加器中,甚至不能将数组索引或循环计数器保存在任何可以直接使用的地方。
由于x86是为使用寄存器而设计的,因此您不能完全避免它们,即使您想这样做并且不关心性能。
当前的x86 CPU在每个时钟周期内可以读/写的寄存器比内存位置多得多。
例如,英特尔Skylake可以在每个周期内对其32KiB 8路关联L1D高速缓存执行两次加载和一次存储(最好的情况),但可以
每个时钟读取10个以上的寄存器,并写入3或4个寄存器(加上EFLAGS。
构建一个L1D缓存,其读/写端口数与the register file将会非常昂贵(在晶体管计数/面积和功率使用方面),特别是如果你想保持它的大小。它可能只是在物理上不可能构建一个可以像x86使用寄存器那样使用内存的东西,并且具有相同的性能。
此外,写入寄存器然后再次读取它基本上具有零延迟,因为CPU检测到这一点,并绕过写回阶段,将结果直接从一个执行单元的输出转发到另一个执行单元的输入。(请参阅
https://en.wikipedia.org/wiki/Classic\_RISC\_pipeline#Solution\_A.\_Bypassing)。
这些执行单元之间的结果转发连接被称为“旁路网络”或“转发网络”,对于寄存器设计来说,对于CPU来说,这样做要比所有东西都必须进入内存再返回要容易得多。CPU只需检查3到5位寄存器编号,而不是32位或64位地址,即可检测需要立即将一条指令的输出作为另一操作的输入的情况。(这些寄存器编号被硬编码到机器代码中,因此它们立即可用。)
正如其他人所提到的,3位或4位对寄存器进行寻址使机器代码格式比每条指令都有绝对地址时要紧凑得多。
另请参阅
https://en.wikipedia.org/wiki/Memory\_hierarchy:您可以将寄存器看作是一个小的快速寄存器固定大小
独立于主内存的内存空间,其中只支持直接绝对寻址。(你不能“索引”一个寄存器:给定一个整数N在一个寄存器中,您无法获取
Nth寄存器中有一个insn。)寄存器也是单个CPU核心的私有寄存器,因此乱序执行可以对它们做任何它想做的事情。对于内存,它必须担心其他CPU核心看到的事情的顺序。拥有固定数量的寄存器是让CPUs完成任务的一部分。
register-renaming用于无序执行。在对指令进行解码时立即获得寄存器编号也可以使这一点变得更容易:永远不会对未知的寄存器进行读或写操作。请参见
为什么mulss在Haswell上只需要3个周期,与Agner的指令表不同?(使用多个accumulators)展开FP循环
关于寄存器重命名的解释,以及一个具体的例子(对问题/我答案后面部分的编辑,显示了使用多个累加器展开以隐藏FMA延迟的加速,即使它重复使用相同的架构寄存器)。
带有存储转发的存储缓冲区基本上给了你“内存重命名”。对存储器位置的存储/重新加载独立于先前的存储并从该核心内加载到该位置。(
(Can a speculatively executed CPU branch contain opcodes that access RAM?)使用堆栈参数调用约定的重复函数调用,和/或通过引用返回值,是堆栈内存的相同字节可以多次重用的情况。
即使第一个存储仍在等待其输入,秒存储/重新加载也可以执行。(我已经在Skylake上测试过了,但IDK如果我在任何地方发布结果的话。)
发布于 2010-03-02 13:01:45
访问寄存器方式比RAM内存更快,因为你不需要访问“慢”的内存总线!
https://stackoverflow.com/questions/2360997
复制相似问题