6.2.3. 最佳化二阶与更高阶cache存取
关于一阶cache的最佳化所说的一切也适用于二阶与更高阶cache存取。有两个最后一阶cache的额外面向:
- cache错失一直都非常昂贵。L1 错失(希望)频繁地命中 L2 与更高阶cache,于是限制其损失,但最后一阶cache显然没有后盾。
- L2 cache与更高阶cache经常由多颗处理器核与/或 HT 所共享。每个执行单元可用的有效cache大小因而经常小于总cache大小。
为了避免cache错失的高成本,工作集大小应该配合cache大小。若是资料只需要一次,这显然不是必要的,因为cache无论如何都没有效果。我们要讨论的是被需要不只一次的资料集的工作负载。在这种情况下,使用一个太大而不能塞得进cache的工作集将会产生大量的cache错失,即使预取成功地执行,也会拖慢程序。
即使资料集太大,一支程序也必须完成它的职责。以最小化cache错失的方式完成工作是程序开发者的职责。对于最后一阶cache,是可能 –– 如同 L1 cache –– 以较小的部分来执行工作的。这与表 6.2 最佳化的矩阵乘法非常雷同。不过,有一点不同在于,对于最后一阶cache,要处理的资料区块可能比较大。如果也需要 L1 最佳化,程序会变得更加复杂。想像一个矩阵乘法,其资料集 –– 两个输入矩阵与输出矩阵 –– 无法同时塞进最后一阶cache。在这种情况下,或许适合同时最佳化 L1 与最后一阶cache存取。
众多处理器世代中的 L1 cache行大小经常是固定的;即使不同,差异也很小。假设为较大的大小是没什么大问题的。在有著较小cache大小的处理器中,会用到两个或更多cache行、而非一个。在任何情况下,写死cache行大小、并为此最佳化程序都是合理的。
对于较高层级的cache,若程序是假定为一般化的话,就不是这样。那些cache的大小可能有很大的差异。八倍或更多倍并不罕见。将较大的cache大小假定为预设大小是不可能的,因为这可能表示,除了那些有著最大cache的机器之外,程序在所有机器上都会表现得很差。相反的选择也很糟:假定为最小的cache,代表浪费掉 87% 或者更多的cache。这很糟;如同我们能从图 3.14 看到的,使用大cache对程序的速度有著巨大的影响。
这表示程序必须动态地将自身调整为cache行大小。这是一种程序特有的最佳化。我们这里能说的是,程序开发者应该正确地计算程序的需求。不仅资料集本身需要,更高层级的cache也会被用于其它目的;举例来说,所有执行的指令都是从cache载入的。若是使用函式库里头的函式,这种cache的使用可能会加总为一个可观的量。那些函式库函式也可能需要它们自己的资料,进一步减少可用的memory。
一旦我们有一个memory需求的公式,我们就能够将它与cache大小作比较。如同先前所述,cache可能会被许多其它处理器核所共享。目前34,在没有写死知识的情况下,取得正确资讯的唯一方法是透过 /sys 档案系统。在表 5.2,我们已经看过系统核心发布的有关于硬件的资讯。程序必须在目录:
/sys/devices/system/cpu/cpu*/cache
找到最后一阶cache。这能够由在这个目录里的层级档案中的最高数值来辨别出来。当目录被识别出来时,程序应该读取在这个目录中的 size 档案的内容,并将数值除以 shared_cpu_map 档案中的位元遮罩中设置的数字。
以这种方式计算的值是个安全的下限。有时一支程序会知道多一些有关其它执行绪或行程的行为。若是那些执行绪被排程在共享这个cache的处理器核或 HT 上、并且已知cache的使用不会耗尽它在总cache大小中所占的那份,那么计算出的限制可能会太小,而不是最佳的。是否要比公平共享应该使用的还多,真的要视情况而定。程序开发者必须做出抉择,或者必须让使用者做个决定。
34. 当然很快就会有更好的方法! ↩