存储器山

存储器山测试原理

为了说明存储器山的测试程序的原理，首先简要介绍局部性和存储器层次结构这两个重要概念，然后介绍存储器山的定义和测试程序的原理模型。

局部性

通常，一个编写良好的（优化的）程序具有良好的局部性，主要表现在两个方面。
时间局部性（Temporal locality）：如果被访问过的存储器地址在较短时间内被再次访问，则程序具有良好的时间局部性。在一定的时间内，重复访问同一个地址的次数越多，时间局部性越好。换句话说，对同一个地址的两次访问间隔时间越短，时间局部性越好。
空间局部性（Spatial locality）：如果程序访问某个存储器地址后，又在较短时间内访问临近的存储器地址，则程序具有良好的空间局部性。两次访问的地址越接近，空间局部性越好。
局部性在程序中普遍存在。好的程序通常具有好的局部性，是一个普适的原理。

存储器层次结构

不同类型的存储设备，其访问速率相差很大。其基本规律为：访问速率越高的设备，单位容量的成本越高，于是越不容易做得很大。计算机系统的设计者需要在控制成本的同时，尽量提高存储器系统的速率。
受益于局部性的概念，计算机的存储器系统通常设计成一个层次结构，CPU内部的寄存器为该层次结构的最高层----它的容量最小，但速率最高。往下依次为各级cache，主存，磁盘等等。该层次结构的运行策略是：尽量让当前被频繁访问的存储区的内容驻留在较高层存储器，作为代价，把不常访问的存储区的内容置换到较低层存储器。同时，尽量让当前被访问的元素附近的元素也驻留在较高层存储器。一个具有良好的局部性的程序，几乎能总是访问高层的存储器，享受到最高的访问速率。
从程序员的角度来看，要尽量编写具有良好的局部性的程序，以适应层次结构，提高程序运行速率。

存储器山测试程序

具有不同的时间局部性和空间局部性的程序，对存储器层次结构的利用效率是不同的。局部性较好，则能得到较快的访问速率。构造一个存储器测试程序，以不同的时间局部性和空间局部性对存储器进行访问，就能得到存储器系统在不同的局部性下的性能（即访问速率）。以控制时间局部性的变量为x轴，控制空间局部性的变量为y轴，存储器访问速率为z轴，就能得到一个三维图形，它看起来像一座有着山峰，山脊和山坡的小山，即存储器山。
通过存储器山，可以直观地看到具有不同的时间局部性和空间局部性的程序对存储器的访问速率的巨大差别。可作为程序员优化程序的参考。
更加有趣的是，通过分析存储器山的数据，还可以看出存储器系统的部分硬件参数。包括L1数据cache（简称为L1-Dcache）、L2-cache和主存（物理介质通常为SDRAM）各自的最高平均速率，L1-Dcache和L2-cache的容量，甚至L1-Dcache的行尺寸。
不过，存储器山并非万能工具。从中无法看出cache的相联度、访问延迟等参数（这些参数可以用其他测试程序间接得出，但无法从存储器山测试程序中得出）。由于连续密集访问，cache预取（Prefetching）也会被阻塞从而无法体现其效果。
存储器山测试程序的核心就是这样一个循环：
Kernel_loop(elems, stride):
for (i = 0; i < elems; i += stride)
    result = data[i];
其中，data是一个整形数组（整形元素单位为4字节），result是一个寄存器变量。Kernel_loop所做的事情，就是将data数组的内容依次读取到CPU的寄存器中。参数elems表示data数组的尺寸，即工作集（working set）尺寸。参数stride表示访问时的步长，即相邻两次访问的元素的地址间隔。 用Get_readrate()函数来获取特定参数下Kernel_loop的读速率：
Get_readrate(elems, stride):Kernel_loop(elems, stride);           // 为cache预热，避免“冷缺失”
time_start = GetTimer();               // 记录测试开始时间Kernel_loop(elems, stride);          // 执行真正的测试time_end = GetTimer();                // 记录测试结束时间time_cost = time_end – time_start;   // 得到时间开销readrate = (elems*sizeof(int)/stride) / time_cost; // 换算为读速率
main()函数改变elems和stride参数的大小，记录Kernel_loop在各个参数下的读速率：
main():
for (elems = MINELEMS; elems <= MAXELEMS; elems <<= 1)
    for (stride = 1; stride <= MAXSTRIDE; stride++)
        Get_ readrate (elems, stride);
以elems为x轴，stride为y轴，readrate为z轴，即可得到一座存储器山。这里，elems控制时间局部性。因为当stride固定时，elems越小，对同一个地址的两次访问（cache预热时一次，真正测试时一次）间隔的时间越短。而stride控制空间局部性。因为stride越小，相邻两次访问的地址间隔越小。readrate即是不同时空局部性下对存储器的读速率。MINELEMS、MAXELEMS、MAXSTRIDE等常量的具体数值根据测试平台的参数来选择。