📜  电脑组织|本地性和缓存友好代码

📅  最后修改于: 2021-06-28 09:45:17             🧑  作者: Mango

高速缓存是为处理数据读取操作中的处理器与内存的差距而建立的较快的内存,即,CPU寄存器中的数据读取操作与主存储器中的数据读取操作中的时间差。寄存器中的数据读取操作通常比主存储器中的读取速度快100倍,并且随着我们在内存层次结构中的不断下降,它的数量还在持续增加。

高速缓存安装在CPU寄存器和主存储器的中间,以弥合此时间间隔中的数据读取。高速缓存用作存储在相对较慢的主存储器中的数据和指令的子集的临时暂存区。由于高速缓存的大小很小,因此仅将处理器在程序执行期间经常使用的数据存储在高速缓存中。 CPU对这些经常使用的数据进行缓存,从而无需一次又一次地从速度较慢的主存储器中提取数据,这需要花费数百个CPU周期。

缓存有用的数据中心的想法围绕着称为本地性的计算机程序的基本属性。具有良好局部性的程序倾向于从内存层次结构的上层(即高速缓存)中一次又一次地访问同一组数据项,因此运行速度更快。

示例:执行相同数量的算术运算但具有不同局部性的不同矩阵乘法内核的运行时间可能相差20倍!

地区类型:

  • 时间地点–
    时间局部性指出,相同的数据对象很可能在程序执行期间被CPU多次重用。一旦在第一次未命中数据对象时将其写入高速缓存,就可以预期该对象上的许多后续命中。由于缓存比下一个较低级别的存储(如主内存)要快,因此这些后续命中可以比原始未命中更快地得到服务。
  • 空间局部性–
    它指出,如果一个数据对象被引用一次,那么很有可能在不久的将来也将引用它的邻居数据对象。内存块通常包含多个数据对象。由于空间上的局限性,我们可以预期,未命中后复制块的成本将由对该块内其他对象的后续引用来摊销。

位置的重要性–
程序的局部性对硬件和软件系统的设计和性能有着巨大的影响。在现代计算系统中,基于位置的优势不仅限于体系结构,而且操作系统和应用程序的构建方式可以充分利用本地性。

在操作系统中,局部性原则允许系统将主内存用作虚拟地址空间中最近引用的块的高速缓存,并且在磁盘文件系统中最近使用过磁盘块的情况下也可以使用。

同样,Web浏览器之类的应用程序通过在本地磁盘上缓存最近引用的文档来利用时间局部性。大量的Web服务器将最近请求的文档保存在前端磁盘缓存中,而无论服务器如何干预,这些文档都可以满足对这些文档的请求。

缓存友好代码–
具有良好局部性的程序与具有较差局部性的程序相比,具有较低的高速缓存未命中率,因此通常可以更快地运行。在良好的编程习惯中,缓存性能始终被视为分析程序性能的重要因素之一。有关代码如何实现缓存友好的基本方法是:

  • 经常使用的情况需要更快:程序通常将大部分时间都花在几个核心功能上,而这些功能反过来与循环有很大关系。因此,应以具有良好局部性的方式设计这些循环。
  • 多个循环:如果一个程序包含多个循环,则将内部循环中的高速缓存未命中率降至最低,以减轻代码的性能。

示例1:可以通过遵循多维数组代码的简单示例来理解上述上下文。考虑sum_array()函数,该函数以行优先的顺序对二维数组的元素求和:

int sumarrayrows(int a[8][4])
{
 int i, j, sum = 0;
 for (i = 0; i < 8; i++)
    for (j = 0; j < 4; j++)
     sum += a[i][j];
 return sum;
}

假设高速缓存的块大小为4个字,字大小为4个字节。它最初是空的,并且由于C以行优先的顺序存储数组,因此引用将导致以下命中和未命中模式,而与缓存组织无关。

包含w [0] –w [3]的块从内存加载到缓存中,对w [0]的引用未命中,但接下来的三个引用都是命中。对v [4]的引用会导致另一个未命中,因为新块被加载到缓存中,接下来的三个引用为命中,依此类推。通常,四分之三的引用将会命中,这是使用冷缓存可以做到的最好的结果。因此,点击率是3/4 * 100 = 75%

示例2:现在,sum_array()函数以列优先顺序对二维数组的元素求和。

int sum_array(int a[8][8])
{
 int i, j, sum = 0;
 for (j = 0; j < 8; j++)
   for (i = 0; i < 8; i++)
   sum += a[i][j];
 return sum;
}

该程序的缓存布局将如下图所示:

由于C按行优先顺序存储数组,但在这种情况下按列优先顺序访问数组,因此在这种情况下局部性变坏了。引用将按以下顺序进行:a [0] [0],a [1] [0],a [2] [0],依此类推。随着高速缓存的大小变小,由于程序的本地性较差,每个引用都会丢失。因此,命中率将为0。不良的命中率最终将降低程序的性能,并导致执行速度变慢。在编程中,应避免这些类型的实践。

结论 –
在讨论现实生活中的应用程序和编程领域时,即使程序的运行时复杂度很高,优化的缓存性能也可以使程序获得良好的加速。一个很好的例子是快速排序。尽管它具有O(n 2 )的最坏情况复杂度,但是它是最受欢迎的排序算法,并且重要的因素之一是与许多其他排序算法相比更好的缓存性能。应该以一种可以最大程度地利用缓存来加快执行速度的方式编写代码。