存储器层次②:局部性与高速缓存

本文探讨了计算机程序中的局部性原理,包括时间局部性和空间局部性,并通过示例说明了如何通过良好的顺序访问模式提高性能。重点讲解了如何利用高速缓存策略,如直接映射法,来优化内存访问,提升程序执行效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

局部性

一个计算机程序常常具备良好的局部性。其含义是程序在未来总是倾向于访问那些最近使用过的数据周围附近的数据,或者是引用那个最近使用的数据本身。这就是局部性原理。
局部性通常包含两种形式:

①:时间局部性:一个被引用的内存位置在不久的将来很可能继续被访问
②:空间局部性:一个内存位置被引用,其周围的附近位置很可能不久被访问。

具备良好的局部性特性的程序通常有更好的性能。原因在于计算机系统中采取缓存保存那些最近使用的数据,那么在下一次访问到该数据时将大大缩短时间。另外,一些系统也采取预加载的技术,一次将被引用位置周围数据都进行缓存,那么具备良好空间局部性的程序在未来的访问速度也将更快。

int fun(int v[],int n)
{
        int sum=0;    //sum将一直被访问,具备时间局部性 通常存储于寄存器中
        for(int i=0;i<n;i++)
        {
                sum+=v[i];  //v[]具备很好的空间局部性 一个元素周围的元素将来继续被访问
        }
        return sum;   
}
~    

该段程序中 sum具备良好的时间局部性 而对v[]的访问则具备良好的空间局部性

通常针对循环此类的引用方式,其称为顺序引用模式,步长为1的引用方式将具备良好的空间局部性,随着步长的增加,其空间局部性优点将下降。
良好的顺序访问模式将影响程序的执行性能,一种比较良好的设计方式是:

引用的顺序与内存中的顺序一致。(类比于数据库聚集索引B+树上范围查找和排序的优良特性)

一个具有良好引用顺序的二维数组遍历

int fun(int v[][],int nm,int n)
{       
        int sum=0;    //sum将一直被访问,具备时间局部性 通常存储于寄存器中
        for(int i=0;i<m;i++)
                for(int j=0;j<n;j++)
                {
                        sum+=v[i][j];
                }
        return sum;
}
~      
访问下标a[0][0]a[0][1]a[0][2]a[1][0]a[1][1]a[2][2]
访问顺序123456
访问地址048121620

可以看到对v[][]的访问顺序与它们在内存中的顺序一致,那么整块整块的缓存将带来性能提升。

一个访问顺序与内存中元素顺序不一致的例子:

int fun(int v[][],int nm,int n)
{
        int sum=0;    //sum将一直被访问,具备时间局部性 通常存储于寄存器中
        for(int i=0;i<n;i++)
                for(int j=0;j<m;j++)
                {
                        sum+=v[j][i];
                }
        return sum;
}
~                                                                                                  
~   
访问下标a[0][0]a[1][0]a[2][0]a[0][1]a[1][1]a[2][1]
访问顺序123456
访问地址0122441628

可以看到由于内层循环为j:0=>m 但实际访问的元素在内存中顺序不是顺序的,而是打乱的。因此缓存预加载将失效。

因此对于循环若需要达到良好的空间局部性,那么需要使得内层循环尽可能访问一个局部的空间

存储器层次中的缓存

高速缓存cache是一个容量较小,但却非常快速的存储设备,其作为下一层低速存储设备的缓存。计算机系统中各级别的存储层都可以看做缓存,典型的:内存作为磁盘的缓存,磁盘作为网络数据的缓存。通常针对缓存的访问,主要包含三大类区别:

1.缓存命中:当计算机访问第k+1层的数据对象时,发现第k层中的缓存已经包含了该数据对象,那么将发生缓存命中,直接在第k层返回该数据即可。

2.缓存不命中:即在第k层并没有缓存第k+1层需要访问的数据对象。那么可能执行一个替换,即将第k+1层的数据加载进第k层,同时由于可能存在空间不足,需要置换出一些块。常见的置换策略如LRU算法。

一个需要解决的问题是,来自第k+1层的数据应该缓存在第k层的哪个位置?

硬件实现的缓存通常采取严格的放置策略,第k+1层的数据块只能放在第k层的一个子集中。例如一种可能的策略采取一个hash算法将多个k+1层数据块映射到第k层的一个数据块。值得注意的是:通常对于内层循环,将其访问的数据称为工作集,如果缓存的大小比工作集更小,容易造成缓存不命中。

高速缓存存储器

早期的计算机只包含三大类别的存储层次:寄存器、内存、磁盘
不过随着CPU速度与内存访问的速度之间的差距渐渐拉大,内存的访问速度称为系统性能的短板。系统的设计者在CPU与主内存之间安插多组容量很小、速度很快的SRAM高速缓存存储器。即L1 L2 L3缓存。

一个计算机系统,其中存储器的地址有m位,那么一共具有2的m次方个不同的地址。一个机器的高速缓存被组织成2的s次方个缓存数组,每一个缓存数组中包含E个高速缓存行,而每一行中由2的b字节数据缓存块组成,另外还包括1个有效位指明该行缓存是否有效(典型的,机器启动时,缓存还没开始缓存数据)另外还包含一个t=m-(b+s)个标记位。唯一标识存储在高速缓存行中的块。

在这里插入图片描述
在这里插入图片描述

高速缓存(S,E,B,m)描述一个组织结构

<高速缓存映射算法:直接映射法>

假定计算机系统中,包含一个CPU、一个寄存器文件、一个L1高速缓存、一个主存。一次典型的CPU读取内存的过程如下:

①首先向L1缓存请求这个字w 如果高速缓存中包含了这个字的副本,那么直接返回
②如果不包含,那么L1高速缓存请求MMU向主内存请求将包含w的数据块缓存进L1
③最后L1缓存将这个字w返回给CPU

那么CPU请求的内存地址,L1高速缓存如何快速的判断是否命中以及返回请求的字。
在这里插入图片描述

从直接的高速缓存中,可以看到一个地址请求高速缓存的过程核心是几个步骤:
①:根据地址中间部分的组索引,确定所命中的组
②:地址中标记位与当前缓存组的标记位比对确定是否命中
③:最后根据地址中块偏移,定位到实际数据字所在位置,返回数据字

由此可以的到缓存的几个特点:

对于组索引相同、标记位相同的地址,它们在内存中临近(地址前面部分相同),它们作为整体映射进缓存
对于组索引相同,但标记位不同的地址集合,它们对应着相同的组,但同一时间只能一个集合在缓存中
由此可见:
①缓存中的数据总是为内存中数据的局部部分=》对应于空间局部性
②不同的地址集合映射到同一部分,良好的替换策略成为关键

由此可见,缓存的思想是基于计算机系统运行时的局部性特点。一个具有良好的时间局部性和空间局部性的程序能够很好的使用缓存来进行加速。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值