Archive for 2013年4月

什么是寄存器

2013/04/06

寄存器 (register) 似乎是初学计算机结构的人多少有所了解的概念 —— 成本最高也是速度最快的存储单元。不过,有时事物还可以从完全不同的角度重新阐述一遍。这段时间在看 SICP 和 Lua 虚拟机,其中两个常被并列提及的概念是 stack-based 虚拟机和 reigster 虚拟机。即使在深入了解虚拟机理论前,也可以想像语言虚拟机结构不太可能与 CPU 硬件 register 有直接对应关系。「Register 虚拟机」这个名称引起我对 register 进行重新认识的兴趣。

接触虚拟机之前还考虑过另一个和 register 相关的问题。一般的常识是函数调用时的参数通过 stack 传递。但是很多编译器从 90 年代起就实现了一种优化:通过 CPU register 传递参数。这样做的优点固然显而易见,但同时引出新的疑问:原本用 stack 传递参数不仅解决了数据传递问题,也解决了多层嵌套的函数调用时上层数据的保存问题;而 register 数量有限,如何解决多层嵌套调用时的状态问题?答案其实也简单,当一个函数 A 调用另一个函数 B 时,那些正在存储 A 本身的参数或者 A 本身的局部变量的 register 如果又要被用于向 B 传递参数,那么这些 register 的内容在被更改为传递给 B 的参数之前会先被压入 stack,B 返回之后这些 register 的内容会从 stack 中恢复。也就是说,register 并不是替代 stack 的传参功能,而是对 stack 的使用延迟了一个调用层次。根据「局部性原理」延迟使用主存中的 stack 得到速度优化。

由此可见,register [1] 虽然经常被称为比主存更快的存储单元,但是它的作用并不是给任意用途的主存单元提供高速替代品,而是和 stack 这种特定用途的存储区域有更紧密的联系。若感觉如此得出结论过于仓促,可以再观察几个例子。比如说,操作系统进行线程切换时,需要切换当前 stack 以及 —— 所有 register 内容。看,register 就像 stack 的延展。我们可以说 register 是 stack 最顶一段的硬件加速机制。

接下来再看 Lua 虚拟机的实现,它的 VM register 就是实现在 VM stack [2] 上。也就是说,Lua 使用 VM stack 的方式其实和一般的 C 编译器产生的代码使用 CPU stack 的方式没有区别。那么究竟为何 Lua 之类的虚拟机区别于 stack-based 虚拟机而特地被称为 register 虚拟机呢?通过 stack-base 虚拟机的指令可以发现,这种虚拟机是基本严格按照后入先出的方式使用 stack,每条指令基本上只操作 stack top 的一两条数据。而 register 虚拟机只在大的调用层次上遵循后入先出,在每层函数运行中是完全随机地使用本层函数的 stack (即当前 stack-frame [3],函数返回地址部分除外)。

全新的 register 的本质定义是:可被随机访问的最顶层 stack-frame。由此可以看出,主流编译语言几乎都可以称为 register 虚拟机 [4] 实现,即使在使用 register 传参这个优化流行之前。CPU 的硬件 register 是对一般 register 的硬件优化。

脚注:

  1. 更确切地说是通用寄存器,general-purpose registers 。
  2. Lua 的 VM stack 和其虚拟机实现本身的 C runtime stack (或者说硬件 CPU 的 stack) 是分开的。
  3. 按照函数调用层次,stack 可以被看作一组 frame,每层函数调用占据一个 frame。
  4. 这里的「虚拟机」是真实的 CPU。