一、编写高速缓存友好的代码
确保代码高速缓存友好的基本方法:
1) 让最常见的情况运行得快。程序通常把大部分时间都花在少量的核心函数上,而这些函数通常把大部分时间都花在了少量循环上。所以要把注意力集中在核心函数里的循环上,而忽略其他部分。
2) 尽量减小每个循环内部的缓存不命中数量。在其他条件(例如加载和存储的总次数)相同的情况下,不命中率较低的循环运行得更快。
二、综合:高速缓存对程序性能的影响
1.存储器山
一个程序从存储系统中读数据的速率称为读呑吐量(read throughput), 或者有时称为读带宽(read bandwidth)。如果一个程序在 s 秒的时间段内读 M 个字节,那么这段时间内的读吞吐量就等于n/s,通常以兆字节每秒(MB/s)为单位。
那么测量出的读吞吐量能让我们看到对于这个读序列来说的存储系统的性能。下图给出一个示例:
这个示例中,run函数是一个包装函数, 调用test 函数,并返回测量出的读吞吐量。
如果我们反复以不同的size和stride值调用 run 函数,那么我们就能得到一个读带宽的时间和空间局部性的二维函数,称为存储器山。
每个计算机都有表明它存储器系统的能力特色的唯一的存储器山。例如,图6-41展 示了Intel Core i7 系统的存储器山。
这座Core i7 山的地形地势展现了一个很丰富的结构。垂直于大小轴的是四条山脊,分别对应于工作集完全在L1高速缓存、L2高速缓存、L3高速缓存和主存内的时间局部性区域。
2.重新排列循环以提高空间局部性
分块(blocking)可以提高内循环的时间局部性。分块的大致思想是将一个程序中的数据结构组织成的大的片(chunk),称为块(block)。这使得能够将一个片加载到 L1 高速缓存中,并在这个片中进行所需的所有的读和写。由于 Core i7 有完善的预取硬件,分块不会提高矩阵乘在 Core i7 上的性能
3.在程序中利用局部性
有良好局部性的程序从快速的高速缓存存储器中访问它的大部分数据。局部性差的程序从相对慢速的DRAM主存中访问它的大部分数据。
理解存储器层次结构本质能够利用这些知识编写出更有效的程序:
将注意力集中在内循环上,大部分计算和内存访问都发生在这里。
通过按照数据对象存储在内存中的顺序、以步长为1的来读数据,从而使得程序中的空间局部性最大。
一旦从存储器中读人了一个数据对象,就尽可能多地使用它,从而使得程序中的时间局部性最大。
三、链接
链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。
链接可以执行于编译时,也可以执行于加载时,甚至执行于运行时。在现代系统中,链接是由叫做链接器(linker)的 程序自动执行的。
理解链接的意义:
1)理解链接器将有助于构造大型程序。
2)理解链接器将避免一些危险的编程错误。
3)理解链接将有助理解语言的作用域规则是如何实现的。
4)理解链接将帮助理解其他重要的系统概念。
5)理解链接将使你能够利用共享库。
1. 编译器驱动程序
大多数编译系统提供编译器驱动程序(compiler driver),它代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。
图7-2 概括了驱动程序在将示例程序从 ASCII 码源文件翻译成可执行目标文件时的行为。
2. 静态链接
为了构造可执行文件,链接器必须完成两个主要任务:
符号解析(symbol resolution)。目标文件定义和引用符号,每个符号对应于一个函数、一个全局变量或一个静态变量(即C语言中任何以static属性声明的变量)。 符号解析的目的是将每个符号引用正好和一个符号定义关联起来。
重定位(relocadon)。编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。链接器使用汇编器产生的重定位条 目(relocation entry)的详细指令,不加甄别地执行这样的重定位。
3.目标文件
目标文件有三种形式:
可重定位目标文件。包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。
可执行目标文件。包含二进制代码和数据,其形式可以被直接复制到内存并执行。
共享目标文件。一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载进内存并链接。
编译器和汇编器生成可重定位目标文件(包括共享目标文件)。链接器生成可执行目标文件。从技术上来说,一个目标模块(object module)就是一个字节序列,而一个目标文件(object file)就是一个以文件形式存放在磁盘中的目标模块。