- 作者: 雪山肥鱼
- 时间:20210316 06:31
- 目的:初步了解 cache
# 1. cache 缓存
# 2. L1 cache 结构
# 2.1 对于24bit 的疑问
# 3. cache trashing 抖动
# 4. 缓存的一致性
# 5. False Sharing
相关阅读链接:
https://coolshell.cn/articles/20793.html 陈皓
https://github.com/haoel/cpu-cache 陈皓 git
https://blog.csdn.net/qq_27633421/article/details/106213665
本文尝试翻译:图片均来自于此帖子
https://manybutfinite.com/post/intel-cpu-caches/
缓存一致性MESI的链接:
https://www.scss.tcd.ie/Jeremy.Jones/VivioJS/caches/MESIHelp.htm
cache 缓存
内存是DRAM,速度慢,容量大。属于CPU外部设备,通过总线与CPU进行通信。而cache 在 CPU内部,属于片上硬件。所以内存的速度会远远慢于cache.
到底差多少:
L1的速度是RAM的27倍。L1,L2都是KB级别的,L3才MB。以Intel core i7-8700k(6core) 来说 每个核上有L1 = 64KB,L2 = 256KB,而共用的L3 = 2*6 = 12MB。
cache利用了时间/空间局部性原理,访问一个地址内容,可能近期也会访问。与此同时,也有可能访问这个内存周围的内存地址。
那么一块内存区域会被加到cache。距离CPU较近的叫做一级cache,较远叫做二级cache. 这只针对一颗CPU的core,L1/L2 cache 并不共享
也存在L3 cache 三级缓存,所有CPU的core 共享。
L1 cache 结构
cache的单位是cacheline,是一块连续的内存。以cacheline 64字节为例。所有的cacheline 又划到各个way旗下。
row: 叫做set
columne: 叫做way
所以总共有512个cell,每个cell 64字节,L1 cache共32kb.
完全关联型cache, 一页内存可以放置在cache中的任意cell里。存储方便,但是查找起来开销太大。
set associate,利用地址的一些信息,在cache里找存储位置。如图中 第6位到第11位就是set 的 index.
但是 这一行的哪一个cell存储了 对应地址的数据呢?那么 就用到了24-bit tag值(这里我也没太明白为什么是24bits,难道是以处理器?原文:The processor can address 64GB)。比如物理内存64GB(2^36 = 64G, L1 cache 能映射64G的内存),那么每一页4kb,那么共有 2^24 pages,那么就需要24bit 来打tag(可以理解为1页1个tag),也就是说前24位相同,就分到同一个Way中。。
当CPU访问一个内存的时候,通过内存中间的6bits定位在哪个set,再通过24bits定位响应的cacheline。类似Hash table 一样,显示O(1)搜索,然后进入冲突里搜索。
确定了在哪个set后,在硬件的支持下,并行也就是同时在8个way里去寻找tag。找到则cache命中。(注意这里,cache只要命中了,就是命中64字节)
否则转移到L2 cache, 若还没命中到L3 中去寻找。L2 L3的原理和L1一样的,只不过会大一些比如64KB,16ways.
总共有4096个row,每个way 有256Kb,总大小4M。18bits for tags, and 12bits for set index.
对于24bits的疑问:来自于陈皓(链接)博客的留言:
cache trashing cache 抖动
内存的一段数据由于被分配到了同一个set, 出现来了compete for same cache. 被频繁的换入换出。即使在cache 还没有用完的情况下还是会出现性能瓶颈。
缓存的一致性
指的是写到缓存了,那么在何时进行更新主存的动作。
可以在MESI的链接里进行学习。玩一下,挺有意思的。
所谓的MESI:
- M Modified (一个core 已经对L1 cache 里的一块内存进行了修改)
- E: Exclusive(独有的,只有这个core的L1 cache 存有这块内存的值)
- S: Shared(其他core 也有)
- I : Invalid (该处内存已经被其他core 修改了)
实际上cache里的每一块 cacheline 附带一个状态机。在MESI里的连接点点就知道了。
MESI升级:MOESI,可以从其他core中的缓存更新自己缓存的数据。
MESIF:陈皓文章中有提到。一个core update缓存后会转发给其他core
False Sharing
用处:可以理解为性能调优,
举例:同一块 cacheline(64bytes)被不同的线程进行写操作。导致所有的线程都在不断地重新同步cahceline.导致多个线程跑不过一个线程。
代码来自陈皓的github:
- 增加了线程独占的临时变量,对临时变量进行操作后,再对全局变量result[id] 进行刷新,大大降低了对全局变量的写入次数。
暂时能想到的方案 - 解决方案 1:多线程中使用局部变量
- 解决方案 2:线程中的全局变量至少隔开一个cacheline,让cacheline之间不会叠加
- 解决方案 3: 内存对齐
理解: 真正理解false sharing 需要对MESI的玩法有所了解。 - 对同一块cache不停的进行写操作,会增加cache之间更新缓存的花销。包括不停的去刷新主存的内容。缓存的cacheline的state machine 频繁的从 S -> I 状态