高速缓存和 TLB

高速缓存可保存转换解析缓冲区 (TLB),TLB 中包含最近使用的指令文本或数据页的虚拟地址到实际地址的映射。

取决于处理器的体系结构和模型,处理器有一个到几个高速缓存来保存下面的数据:

  • 正在执行的程序的各部分
  • 由正在执行的程序使用的数据
  • TLB

如果发生高速缓存未命中,装入一个完整的高速缓存线需要数十个处理器周期。 如果发生 TLB 未命中,计算一个页面虚拟到实际的映射可能需要几十个周期。 精确的成本由执行情况决定。

即使一个程序和它的数据可以存入高速缓存,使用的行数或 TLB 入口越多(即引用局部性越低),装入所有内容所需的 CPU 周期越多。 除非指令和数据多次重复使用,否则装入它们所需的开销占程序总执行时间的很大一部分,这导致系统性能降低。

好的编程技术可以保证程序的主体和典型事例流尽可能小。 主过程及其频繁调用的所有子例程应该是相邻的。 可能性小的情况,如隐蔽的错误,应该只经过主体中的测试。 如果情况真的发生,那么应该在单独的子例程中进行处理。 所有这类子例程在模块的结束处应该组合在一起。 这种安排减少了使用率低的代码占用使用率高的高速缓存线中的空间的可能性。 在大模块中,一些或者所有使用率低的子例程可能会占用几乎从来不必读入内存的页面。

同样的原则对数据结构也适用,尽管有时有必要更改代码来补偿编译器关于数据布局的规则。

例如,一些矩阵运算,像矩阵乘法,包含有那些如果简单编码则引用局部性就差的算法。 矩阵运算通常包括顺序存取矩阵数据,例如作用于列元素的行元素。 每个编译器都有关于存储器矩阵布局的特殊规则。 FORTRAN 编译器按列为主的格式布置矩阵(即第 1 列的所有元素后面是第 2 列的所有元素,以此类推)。 C 编译器按行为主的格式布置矩阵。 如果矩阵很小,那么行元素和列元素可以包含在数据高速缓存中,处理器和浮点单元可以最快速度运行。 但是,随着矩阵大小的增加,这种行/列运算引用的局部性变差,最终会差到使数据不能再保持在高速缓存中。 实际上,行/列运算的自然存取模式产生高速缓存的系统颠簸模式,其中存取的元素字符串比高速缓存大,会强制初始存取的元素跳出,然后对相同数据再次重复此存取模式。

此类矩阵存取模式的常规解决方案是将运算划分为块,这样相同元素的多重运算可以在当它们还保存在高速缓存中时执行。 这种常规技术叫做条状提取

数值分析专家被要求对矩阵操作算法的版本进行编码,这些算法利用条状提取和其他优化技术。 结果是矩阵乘法性能得到 30 倍的提高。 调整后的例程位于基本线性代数子例程 (BLAS) 库 /usr/lib/libblas.a中。 一组较大的性能调整子例程是 Engineering and Scientific Subroutine Library (ESSL) 许可程序。

要使用库,必须安装 FORTRAN 运行时环境。 通常,用户应该使用该库来进行矩阵和向量运算,因为子例程经过调整可达到用户自己不可能达到的程度。

如果数据结构由程序员控制,那么可能会有其他功效。 常规原则是在可能的时候将频繁使用的数据打包在一起。 如果一个结构中包含频繁存取的控制信息和偶尔存取的详细数据,请确保控制信息是以连续的字节分配的。 这会增加在所有控制信息装入到高速缓存中时产生单个(或至少最小数目)高速缓存未命中的可能性。