缓存命中

本文探讨了游戏开发中使用标准模板库(STL)的问题及内存管理挑战,详细介绍了缓存(Cache)的概念、作用及其对计算机性能的影响。此外,还提供了优化缓存访问的实用技巧。

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

缓存命中

游戏开发者可分为两类:在他们的游戏引擎中使用STL模板库之类的。以及不使用的。一些开发者认为STL内存分配模式(memory allocation pattern)不高效,也导致内存碎片问题,使STL不能在游戏中使用。一些开发者认为STL的强大和方便超过它的问题,而且大部分的问题还是可以变通解决,笔者个人认为STL在PC上可以无障碍使用。因为PC上可以无障碍使用虚拟内存(virtual memory)系统,谨慎的分配内存变得不那么紧要(虽然游戏开发者仍要非常谨慎)。在游戏主机上,只有有限(甚至没有)虚拟内存功能,而且内存命中失败(cache miss)的代价极高,游戏开发者最好编写自定义的数据结构,保证是可预期或者有限的内存分配模式。(在PC上做同样的事情,肯定也不也错不了。)【以上内容来自《游戏引擎架构》第29页】

什么是Cache?

Cache Memory也被称为Cache,是存储器子系统的组成部分,存放着程序经常使用的指令和数据,这就是Cache的传统定义。从广义的角度上看,Cache是快设备为了缓解访问慢设备延时的预留的Buffer,从而可以在掩盖访问延时的同时,尽可能地提高数据传输率。 快和慢是一个相对概念,与微架构(Microarchitecture)中的 L1/L2/L3 Cache相比, DDR内存是一个慢速设备;在磁盘 I/O 系统中,DDR却是快速设备,在磁盘 I/O 系统中,仍在使用DDR内存作为磁介质的Cache。在一个微架构中,除了有L1/L2/L3 Cache之外,用于虚实地址转换的各级TLB, MOB( Memory Ordering Buffers)、在指令流水线中的ROB,Register File和BTB等等也是一种Cache。我们这里的Cache,是狭义 Cache,是CPU流水线和主存储器的 L1/L2/L3 Cache。

好处是什么

提高计算机的运行速度。只要能存储数据的器件都可以称之为存储器,它的含义覆盖了寄存器,缓存,内存,硬盘。cpu访问快慢的速度依次为寄存器-> 缓存->内存->硬盘。
寄存器是中央处理器的组成部分,是一种直接整合到cpu中的有限的高速访问速度的存储器,它是有一些与非门组合组成的,分为通用寄存器和特殊寄存器。cpu访问寄存器的速度是最快的。那为什么我们不把数据都存储到寄存器中呢,因为寄存器是一种容量有限的存储器,并且非常小。因此只把一些计算机的指令等一些计算机频繁用到的数据存储在其中,来提高计算机的运行速度。
缓存其实是内存中高速缓存(cache),它之所以存在,是因为当cpu要频繁访问内存中的一些数据时,如果每次都从内存中去读,花费的时间会更多,因此在寄存器和内存之间有了缓存,把cpu要频繁访问的一些数据存储在缓冲中,这样效率就会更高,但需要注意的是,缓冲的大小也是很小的,不能存放大量的数据,并且缓存中存放的数据会因为cpu的访问而被替代,必须某个数据开始被cpu频繁访问,但后来不再频繁,那这个数据的空间会被其他访问频繁的数据所占据(那些数据会被暂时存储在缓存中是算法问题)。缓存又可以分为一级和二级缓存,一级的速度大一二级的速度。因此cpu在访问数据时,先到缓存中看有没有,没有的话再到内存中读取。

作为一个程序员,你需要理解存储器层次结构,因为它对应用程序的性能有着巨大的影响。如果你的程序需要的数据存储在CPU寄存器中的,那么在指令的执行期间,在零个周期内就能访问到它们。如果存储在高速缓存中,需要1~30个周期。如果存储在主存中,需要50~200个周期。而如果存储在磁盘上,需要大约几千万个周期。【以上内容来自《深入理解计算机系统(Computer Systems)》第382页】

如何知道Cache大小

如何知道自己CPU的L2、L3的容量多大呢?当然可以用CPU-z,但其实可以有个更加简单的办法,在命令行输入:
wmic cpu get L2CacheSize,L3CacheSize
我的笔记本得到这个结果:

怎么去写出缓存友好的代码

struct stu3
{
  char c1;// 偏移量为0符合要求,首位本身不需要偏移。
  int i;  // 偏移量为4, 结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍),所以1+3
  char c2;// 偏移量为8(偏移量4+int大小4),符合要求
}

总长度不考虑自身对齐的最大的那个值的倍数,所以长度是1+3+4+1 = 9,考虑倍数长度是12
优化后

struct stu3
{
  char c1;// 偏移量为0符合要求,首位本身不需要偏移。
       char c2;// 偏移量为1(偏移量1+char大小1),符合要求
  int i;  // 偏移量为4, 结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍),所以
}

优化后大小是1+1+2+4= 8。
* 让最常见的情况运行得快。大部分时间都花在少量的核心函数上,这些函数常把大部分时间都花在少量循环上。
* 在每个循环内部缓存不命中数量最小。
举例数组循环

int sumarraycols(int a[M][N])
{
    int i,j,sum =0;
    for(i=0;i<M;i++)
        for(j=0;j<N;j++)
            sum += a[i][j];
    return sum;
}
int sumarraycols(int a[M][N])
{
    int i,j,sum =0;
    for(j=0;j<N;j++)
        for(i=0;i<M;i++)       
            sum += a[i][j];
    return sum;
}

第一个写法是常见的写法,缓存比较优好。第二个是修改后的。差异在于第一个是连续的内存块。第二个是每隔一段取一块。第二种miss的概率更高。Cache是CacheLine组成的。
——————————

参考

<think>我们正在讨论与缓存命中相关的性能事件。根据用户之前的对话,我们已经解释了`mem_load_retired.fb_hit`和`mem_load_retired.l1_miss`这两个事件。现在用户想查找更多关于缓存命中相关的事件信息,特别是缓存事件类型和缓存性能分析。 在CPU性能监控中,与缓存相关的事件通常包括:-缓存访问(CacheAccesses)-缓存命中(CacheHits)-缓存命中(Cache Misses)-填充缓冲区事件(Fill BufferEvents)-预取事件(PrefetchEvents)这些事件通常按缓存级别(L1、L2、L3)和类型(数据缓存、指令缓存)进行细分。常见的性能事件包括:1.**L1缓存事件**:- `L1-dcache-loads`:L1数据缓存加载次数- `L1-dcache-load-misses`:L1数据缓存加载未命中次数- `L1-dcache-stores`: L1数据缓存存储次数-`L1-dcache-store-misses`: L1数据缓存存储未命中次数-`L1-icache-load-misses`:L1指令缓存加载未命中次数2. **LLC(LastLevel Cache,通常是L3)事件**:-`LLC-loads`:LLC缓存加载次数- `LLC-load-misses`:LLC缓存加载未命中次数- `LLC-stores`: LLC缓存存储次数-`LLC-store-misses`: LLC缓存存储未命中次数3.**其他相关事件**:- `cache-references`:缓存访问次数(通常指最后一级缓存)- `cache-misses`:缓存命中次数(通常指最后一级缓存)-`mem_load_retired.l1_hit`:已退休的加载指令中L1命中的次数(部分架构支持)-`mem_load_retired.l2_hit`:已退休的加载指令中L2命中的次数-`mem_load_retired.l3_hit`:已退休的加载指令中L3命中的次数- `mem_load_retired.fb_hit`:已退休的加载指令命中填充缓冲区的次数(如前所述) 4. **预取事件**:-`hardware_prefetches`:硬件预取次数- `l1d_pend_miss.pending`:L1数据缓存命中且等待处理的次数(可反映内存子系统压力) 5. **TLB事件**(虽然不是缓存,但通常一起分析):- `dTLB-loads`:数据TLB加载次数- `dTLB-load-misses`:数据TLB加载未命中次数-`iTLB-load-misses`:指令TLB加载未命中次数在Linuxperf工具中,可以使用`perflist`命令列出所有可用的事件。不同架构(Intel、AMD、ARM)支持的事件可能不同。###缓存性能分析的基本方法1.**计算缓存命中率**:缓存命中率通常定义为:$$\text{缓存命中率} =\frac{\text{缓存命中次数}}{\text{缓存访问次数}} \times100\%$$或者使用未命中率:$$\text{缓存命中率} =\frac{\text{缓存命中次数}}{\text{缓存访问次数}}\times100\%$$例如,对于L1数据缓存,我们可以这样计算命中率:$$\text{L1d命中率} =\left(1 -\frac{\texttt{L1-dcache-load-misses}}{\texttt{L1-dcache-loads}}\right)\times100\%$$2.**使用perf进行监控**:示例命令:```bash#监控L1数据缓存命中和加载次数perf stat-eL1-dcache-loads,L1-dcache-load-misses --./your_program#监控LLC未命中和访问次数perfstat -e LLC-loads,LLC-load-misses-- ./your_program```3.**分析工具**:-`perfrecord`+`perf report`:记录并分析事件,可以定位到代码中缓存命中高的函数。-`perf annotate`:进一步查看具体指令的缓存命中情况。 4. **优化方向**:-高L1未命中率:优化数据访问局部性(例如使用更紧凑的数据结构、循环分块)。-高LLC未命中率:考虑数据在内存中的布局(避免随机访问、使用预取)。-高TLB未命中:使用大页(huge pages)或调整数据访问模式。 ###实际案例假设我们有一个C++程序,想分析其缓存性能: ```bash#监控L1和LLC相关事件perfstat -e L1-dcache-loads,L1-dcache-load-misses,LLC-loads,LLC-load-misses --./program#输出结果示例#计算L1d命中率:1 -(L1-dcache-load-misses/ L1-dcache-loads)#计算LLC命中率:1- (LLC-load-misses /LLC-loads)``` 如果发现L1未命中率很高,可以使用`perfrecord`记录事件,然后用`perf report`查看热点函数,再使用`perfannotate`查看具体代码行的未命中情况。 ###注意事项-不同CPU架构的事件名称可能不同,例如在Intel上,LLC事件可能使用`mem_load_uops_retired.l3_miss`等。 -部分事件可能需要内核支持(例如在Linux中需要启用特定的性能监控计数器)。-虚拟化环境中可能无法访问所有事件。 ###总结缓存事件类型丰富,覆盖了从L1到LLC的各个层次,以及预取、TLB等。通过`perf`等工具可以监控这些事件,进而计算缓存命中率,分析程序的内存访问效率,并指导优化[^4]。§§相关问题§§1.如何计算L2缓存命中率?2.在Intel CPU上,有哪些特定的缓存性能事件?3.如何使用`perf annotate`分析具体代码行的缓存命中情况? 4.缓存命中率高的常见优化方法有哪些?</think>### 缓存命中相关性能事件分析 #### 一、核心缓存事件类型 1. **缓存命中事件** - `mem_load_retired.l1_hit`:L1 数据缓存命中的退休加载指令次数 - `mem_load_retired.fb_hit`:填充缓冲区命中次数(硬件预取成功)[^4] - `cache-references`:缓存访问总次数(含所有级别) - `LLC-load-hits`:末级缓存(L3)命中次数 2. **缓存命中事件** - `mem_load_retired.l1_miss`:L1 数据缓存命中的退休加载指令[^4] - `mem_load_retired.l2_miss`:L2 缓存命中次数 - `cache-misses`:总缓存命中次数 - `LLC-load-misses`:末级缓存命中次数 3. **特殊缓冲事件** - `mem_load_retired.l2_hit`:L2 缓存命中次数 - `dTLB-load-misses`:数据 TLB 未命中次数 - `resource_stalls.sb`:存储缓冲区满导致的流水线停顿 #### 二、缓存性能关键指标 1. **缓存命中率计算** $$ \text{L1 命中率} = 1 - \frac{\texttt{mem\_load\_retired.l1\_miss}}{\texttt{L1-dcache-loads}} $$ $$ \text{预取效率} = \frac{\texttt{mem\_load\_retired.fb\_hit}}{\texttt{mem\_load\_retired.l1\_miss}} $$ 2. **延迟影响公式** 平均内存访问延迟: $$ \text{AMAT} = \text{L1\_Hit\_Time} + \text{L1\_Miss\_Rate} \times (\text{L2\_Hit\_Time} + \text{L2\_Miss\_Rate} \times \text{Memory\_Latency}) $$ #### 三、性能分析工具链 1. **Linux `perf` 命令示例** ```bash # 监控L1未命中和填充缓冲命中 perf stat -e mem_load_retired.l1_miss,mem_load_retired.fb_hit -- ./app # 分析L1/L2缓存效率 perf stat -e L1-dcache-load-misses,L1-dcache-loads,LLC-load-misses -- ./app ``` 2. **ARM 平台工具** - DS-5 Streamline:可视化缓存事件分析[^3] - `arm-spe`:ARM 统计性能扩展事件采集 - `Caliper`:低开销硬件计数器采样工具 #### 四、典型优化场景分析 | 事件组合 | 性能含义 | 优化方向 | |--------------------------|-----------------------------------|------------------------------| | 高 `l1_miss` + 低 `fb_hit` | 预取无效,内存延迟暴露 | 改进数据局部性,调整预取策略 | | 高 `l1_miss` + 高 `fb_hit` | 预取有效缓解延迟 | 维持当前策略,关注L2/L3效率 | | 高 `l2_miss` + 低 `LLC-hits` | 末级缓存效率低 | 优化数据结构,减少缓存污染 | | 高 `dTLB-load-misses` | 页表遍历开销大 | 使用大页(huge pages) | #### 五、缓存事件关联分析 ```mermaid graph LR A[L1未命中] -->|触发| B[填充缓冲查找] B -->|命中| C[fb_hit] B -->|未命中| D[L2缓存访问] D -->|命中| E[l2_hit] D -->|未命中| F[LLC访问] F -->|未命中| G[内存访问] ``` #### 六、参考监控命令 ```bash # 综合缓存分析 perf stat -e \ mem_load_retired.l1_{loads,miss}, \ mem_load_retired.fb_hit, \ LLC-load{,-misses}, \ dTLB-load-misses \ -- ./application ``` 通过分析这些事件,可定位缓存瓶颈:高 `l1_miss` 需优化数据局部性,低 `fb_hit` 需改进预取策略,高 LLC 未命中需减少缓存污染[^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值