提交 d2d880b8 编写于 作者: W wizardforcel

ch7.

上级 5b02e9be
......@@ -12,7 +12,7 @@
为了理解缓存,你需要理解计算机如何运行程序。你应该学习计算机体系结构来深入理解这个话题。这一章中我的目标是给出一个程序执行的简单模型。
当程序启动时,代码(或者程序文本)通常位于硬盘上。操作系统创建新的进程来运行程序,之后“加载器”将代码从储存器复制到主存中,并且通过调用`main`来启动程序。
当程序启动时,代码(或者程序文本)通常位于硬盘上。操作系统创建新的进程来运行程序,之后“加载器”将代码从存储器复制到主存中,并且通过调用`main`来启动程序。
在程序运行之中,它的大部分数据都储存在主存中,但是一些数据在寄存器中,它们是CPU上的小型储存单元。这些寄存器包括:
......@@ -121,7 +121,7 @@ double get_seconds(){
![](http://greenteapress.com/thinkos/html/thinkos001.png)
图 7.1:数据大小和间隔的平均缺失惩罚函数
图 7.1:数据大小和步长的平均缺失惩罚函数
为了将访问数据的时间分离出来,程序运行了第二个循环,它除了内循环不访问数据之外完全相同。它总是增加相同的变量:
......@@ -154,18 +154,69 @@ Size: 4096 Stride: 32 read+write: 0.7105 ns
Size: 4096 Stride: 64 read+write: 0.7058 ns
```
如果你安装了Python和Matplotlib,你可以使用`graph_data.py`来使结果变成图形。图7.1展示了我运行在Dell Optiplex 7010上的结果。要注意数组大小和间隔以字节为单位表述,并不是数组元素数量。
如果你安装了Python和Matplotlib,你可以使用`graph_data.py`来使结果变成图形。图7.1展示了我运行在Dell Optiplex 7010上的结果。要注意数组大小和步长以字节为单位表述,并不是数组元素数量。
花一分钟来考虑这张图片,并且看看你是否能推断出缓存信息。下面是一些需要思考的事情:
+ 程序多次遍历并读取数组,所以有大量的时间局部性。如果整个数组能放进缓存,平均缺失惩罚应几乎为0。
+间隔是4的时候,我们读取了数组的每个元素,所以程序有大量的空间局部性。如果块大小足以包含64个元素,例如,即使数组不能完全放在缓存中,命中率应为63/64。
+ 如果间隔等于块的大小(或更大),空间局部性应为0,因为每次我们读取一个块的时候,我们只访问一个元素。这种情况下,我们会看到最大的缺失惩罚。
+步长是4的时候,我们读取了数组的每个元素,所以程序有大量的空间局部性。如果块大小足以包含64个元素,例如,即使数组不能完全放在缓存中,命中率应为63/64。
+ 如果步长等于块的大小(或更大),空间局部性应为0,因为每次我们读取一个块的时候,我们只访问一个元素。这种情况下,我们会看到最大的缺失惩罚。
总之,如果数组比缓存大小更小,或间隔小于块的大小,我们认为会有良好的缓存性能。如果数组大于缓存大小,并且间隔较大时,性能只会下降。
总之,如果数组比缓存大小更小,或步长小于块的大小,我们认为会有良好的缓存性能。如果数组大于缓存大小,并且步长较大时,性能只会下降。
在图7.1中,缓存性能对于所有间隔很好,只要数组小于`2 ** 22`字节。我们可以推测缓存大小近似4MiB。实际上,根据规范应该是3MiB。
在图7.1中,缓存性能对于所有步长很好,只要数组小于`2 ** 22`字节。我们可以推测缓存大小近似4MiB。实际上,根据规范应该是3MiB。
间隔为8、16或32B时,缓存性能良好。在64B时开始下降,对于更大的间隔,平均缺失惩罚约为9ns。我们可以推断出块大小为128B。
步长为8、16或32B时,缓存性能良好。在64B时开始下降,对于更大的步长,平均缺失惩罚约为9ns。我们可以推断出块大小为128B。
许多处理器都使用了“多级缓存”,它包含一个小型快速的缓存,和一个大型慢速的缓存。这个例子中,当数组大小大于`2 ** 14`B时,缺失惩罚似乎增长了一点。所以这个处理器可能也拥有一个访问时间小于1ns的16KB缓存。
## 7.5 缓存友好的编程
内存的缓存功能由硬件实现,所以多数情况下程序员都不需要知道太多关于它的东西。但是如果你知道缓存如何工作,你就可以编写更有效利用它们的程序。
例如,如果你在处理一个大型数组,只遍历数组一次,在每个元素上执行多个操作,可能比遍历数组多次要快。
如果你处理二维数组,它以行数组的形式储存。如果你需要遍历元素,按行遍历并且步长为元素大小会比按列遍历并且步长为行的大小更快。
链表数据结构并不总具有空间局部性,因为节点在内存中并不一定是连续的。但是如果你同时分配了很多个节点,它们在堆中通常分配到一起。或者,如果你一次分配了一个节点数组,你应该知道它们是连续的,这样会更好。
类似归并排序的递归策略通常具有良好的缓存行为,因为它们将大数组划分为小片段,之后处理这些小片段。有时这些算法可以调优来利用缓存行为。
对于那些性能至关重要的应用,可以设计适配缓存大小、块大小以及其它硬件特征的算法。像这样的算法叫做“缓存感知”。缓存感知算法的明显缺点就是它们硬件特定的。
## 7.6 存储器层次结构
在这一章的几个位置上,你可能会有一个问题:“如果缓存比主存快得多,那为什么不使用一大块缓存,然后把主存扔掉呢?”
在没有深入计算机体系结构之前,可以给出两个原因:电子和经济学上的。缓存很快是由于它们很快,并且离CPU很近,这可以减少由于电容造成的延迟和信号传播。如果你把缓存做得很大,它就变得很慢。
另外,缓存占据处理器芯片的空间,更大的处理器会更贵。主存通常使用动态随机访问内存(DRAM),每位上只有一个晶体管和一个电容,所以它可以将更多内存打包在同一空间上。但是这种实现内存的方要比缓存实现的方式更慢。
同时主存通常包装在双列直插式内存模块(DIMM)中,它包含至少16个芯片。几个小型芯片比一个大型芯片更便宜。
速度、大小和成本之间的权衡是缓存的根本原因。如果有既快又大还便宜的内存技术,我们就不需要其它东西了。
与内存相同的原则也适用于存储器。闪存非常快,但是它们比硬盘更贵,所以它们就更小。磁带比硬盘更慢,但是它们可以储存更多东西,相对较便宜。
下面的表格展示了每种技术通常的访问时间、大小和成本。
| 设备 | 访问时间 | 通常大小 | 成本 |
| --- | --- | --- | --- |
| 寄存器 | 0.5 ns | 256 B | ? |
| 缓存 | 1 ns | 2 MiB | ? |
| DRAM | 10 ns | 4 GiB | $10 / GiB |
| SSD | 10 µs | 100 GiB | $1 / GiB |
| HDD | 5 ms | 500 GiB | $0.25 / GiB |
| 磁带 | minutes | 1–2 TiB | $0.02 / GiB |
寄存器的数量和大小取决于体系结构的细节。当前的计算机拥有32个通用寄存器,每个都可以储存一个“字”。在32位计算机上,一个字为32位,4个字节。64位计算机上,一个字为64位,8个字节。所以寄存器文件的总容量是100~300字节。
寄存器和缓存的成本很难衡量。它们包含在芯片的成本中。但是顾客并不能直接了解到其成本。
对于表中的其它数据,我观察了计算机在线商店中,通常待售的计算机硬件规格。截至你读到这里为止,这些数据应该已经过时了,但是它们可以带给你在过去的某个时间上,一些关于性能和成本差距的概念。
这些技术构成了“存储器体系结构”。结构中每一级都比它上一级大而缓慢。某种意义上,每一级都作为其下一级的缓存。 你可以认为主存是持久化储存在SSD或HDD上的程序和数据的缓存。并且如果你需要处理磁带上非常大的数据集,你可以用硬盘缓存一部分数据。
## 7.7 缓存策略
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册