3. CPU cache
比起仅仅 25 年前的 CPU,现今的 CPU 复杂得多了。在那时,CPU 核的频率与memory总线在相同的等级。memory存取只比暂存器存取慢了一点点。但这点在 90 年代初期大大地改变了,那时 CPU 设计者提升了 CPU 核的频率,但memory总线的频率以及 RAM 晶片的效能并没有等比例地成长。这不是因为无法发展出更快的 RAM,而是如前一节所解释的。这是可能的,但并不经济。与当前 CPU 核一样快的 RAM,比起任何动态 RAM 都要贵上好几个数量级。
一台有著非常小、非常快的 RAM 的机器,以及一台有著许多相对快速的 RAM 的机器,如果要在两者间择一,在给定超过小小的 RAM 容量的工作集(working set)大小、以及存取硬盘这类次级储存(secondary storage)媒体的成本之后,后者永远是赢家。这里的问题在于次级储存装置 –– 通常是硬盘 –– 的速度,它必须用以保存部分被移出(swap out)的工作集。存取这些硬盘甚至比 DRAM 存取要慢上好几个数量级。
幸运的是,不必做出非全有即全无的(all-or-nothing)选择。一台电脑可以有一个小容量的高速 SRAM,再加上大容量的 DRAM。一个可能的实作会是,将处理器定址空间的某块区域划分来容纳 SRAM,剩下的则给 DRAM。操作系统的任务就会是最佳化地分配资料以善用 SRAM。基本上,在这种情境下,SRAM 是作为处理器的暂存器集的扩充来使用的。
虽然这是可能的实作,但并不可行。忽略将 SRAM memory的实体资源映射到处理器的虚拟(virtual)定址空间的问题(这本身就非常难),这个方法会需要令每个行程(process)在软件上管理memory区域的分配。memory区域的大小因处理器而异(也就是说,处理器有著不同容量的昂贵 SRAM memory)。组成一支程序的每个模组都会要求它的那份快速memory,这会由于同步的需求而引入额外的成本。简而言之,拥有快速memory的获益将会完全被管理资源的间接成本(overhead)给吃掉。
所以,并非将 SRAM 置于操作系统或者使用者的控制之下,而是让它变成由处理器透明地使用与管理的资源。在这种方式下,SRAM 是用以产生主memory中可能不久就会被处理器用到的资料的暂时副本。因为程序码与资料具有时间(temporal)与空间局部性(spatial locality)。这表示,在短时间内,很可能会重复用到同样的程序码或资料。对程序码来说,这表示非常有可能会在程序码中循环(loop),使得相同的程序码一次又一次地执行(空间局部性的完美例子)。资料存取在理想上也会被限定在一小块区域中。即使在短时间内用到的memory并非邻近,同样的资料也有很高的机会在不久后再次用到(时间局部性)。对程序码来说,代表 –– 举例来说 –– 在一轮回圈中会产生一次函式呼叫(function call),这个函式在memory中可能很远,但呼叫这个函式在时间上则会很接近。对资料来说,代表一次使用的memory总量(工作集大小)理想上是有限的,但使用的memory –– 由于 RAM 随机存取的本质 –– 并不是相邻的。理解局部性的存在是 CPU cache概念的关键,因为我们至今仍在使用它们。
一个简单的计算就能看出cache在理论上有多有效。假设存取主memory花费 200 个周期,而存取cachememory花费 15 个周期。接著,程序使用 100 个资料元素各 100 次,若是没有cache,将会在memory操作上耗费 2,000,000 个循环,而若是所有资料都被cache过,只要 168,500 个周期。提升了 91.5%。
用作cache的 SRAM 大小比起主memory小了好几倍。根据作者使用具有 CPU cache的工作站(workstation)的经验,cache的大小总是主memory大小的 1/1000 左右(现今:4MB cache与 4GB 主memory)。单是如此并不会形成问题。假如工作集(正在处理的资料集)的大小比cache大小还小,这无伤大雅。但是电脑没理由不拥有大量的主memory。工作集必定会比cache还大。尤其是执行多个行程的系统,其工作集的大小为所有个别的处理器与系统核心的大小总和。
应对cache的大小限制所需要的是,一组能在任何给定的时间点决定什么该cache的策略。由于并非所有工作集的资料都会正好在相同的时间点使用,所以我们可以使用一些技术来暂时地将一些cache中的资料替换成别的资料。而且这也许能在真的需要资料之前就搞定。这种预取会去除一些存取主memory的成本,因为对程序的执行而言,这是非同步进行的。这所有的技术都能用来让cache看起来比实际上还大。我们将会在 3.3 节讨论它们。一旦探究完这全部的技术,协助处理器就是程序开发者的责任了。这些作法将会在第六节中讨论。