slab 学习



cpu cache line 原理

分类: linux memory 6375人阅读 评论(0) 收藏 举报
参考:
一个讲解Direct Mapped Cache非常深入浅出的文章:

CPU cache


=============================================

总体认识, 
cpu的cache通常较大, 比如 128KB, 被划分为多个有固定大小的cache line, cache line通常是32Byte或64Byte.

CPU内部的cache种类, 至少有三种
1) 指令cache
2) 数据cache 通常有多级 multi-level
3) TLB 加速虚拟地址2物理地址转换


cache entry (cache条目)
包含如下部分
1) cache line : 从主存一次copy的数据大小)
2) tag : 标记cache line对应的主存的地址
3) falg : 标记当前cache line是否invalid, 如果是数据cache, 还有是否dirty


cpu访问主存的规律
1) cpu从来都不直接访问主存, 都是通过cache间接访问主存
2) 每次需要访问主存时, 遍历一遍全部cache line, 查找主存的地址是否在某个cache line中.
3) 如果cache中没有找到, 则分配一个新的cache entry, 把主存的内存copy到cache line中, 再从cache line中读取.


cache中包含的cache entry条目有限, 所以, 必须有合适的cache淘汰策略
一般使用的是LRU策略.
将一些主存区域标记为non-cacheble, 可以提高cache命中率, 降低没用的cache


回写策略
cache中的数据更新后,需要回写到主存, 回写的时机有多种
1) 每次更新都回写. write-through cache
2) 更新后不回写,标记为dirty, 仅当cache entry被evict时才回写
3) 更新后, 把cache entry送如回写队列, 待队列收集到多个entry时批量回写.


cache一致性问题
有两种情况可能导致cache中的数据过期
1) DMA, 有其他设备直接更新主存的数据
2) SMP, 同一个cache line存在多个CPU各自的cache中. 其中一个CPU对其进行了更新.


cpu stall cpu失速
指的是当cache miss时(特别是read cache miss), cpu在等待数据从内存读进去cache中期间, 没事可做.
解决此问题的方法有
1) 超线程技术. CPU在硬件层面, 把一个CPU模拟成两个CPU, 在上层看来是两个CPU. 并发的执行两个线程. 这样当一个线程因cache miss在等待时, 另一个线程可以执行.


主存的一个地址, 需要被映射进哪个cache line? (术语:Associativity)
根据映射策略的不同而不同


1) 最笨的, 一个地址可被映射进任意cache line (fully associative)
   带来的问题是, 当寻找一个地址是否已经被cache时, 需要遍历每一个cache line来寻找, 这个代价不可接受.
   就像停车位可以大家随便停一样, 停的时候简单的, 找车的时候需要一个一个停车位的找了.
   你想下, cpu想知道一个地址是否已经在cache中了, 需要把全部cache line找一边, 那该有多慢?


2) Direct Mapped Cache  (相当于1-way associative)
   这个就是相当于hash了, 每个地址能被映射到的cache line是固定的. 
   每个人的停车位是固定分配好的. 可以直接找到.
   缺点是, 因为人多车少, 很可能几个人争用同一个车位, 导致cache 淘汰频繁. 需要频繁的从主存读取数据到cache, 这个代价也较高.
   由于cache中cache line的个数都是2的指数个. 那么, hash算法就很简单了, 不用取模, 直接把内存地址的某几个bit位拿出来即可. 比如cache line有128(2^7)个, cache line的大小是32(2^5)字节, 
   那么一个32位地址的 0~4位作为cache line内部偏移, 5~11位作为cache line的索引即可. 剩下的bit12~31作为当前cache line的tag. tag的作用时, 当有另外一个地址也映射到同一个cache line时, tag用来比较两个地址是不是同一个地址. 毕竟同一个cache-line可以对应的内存的位置非常多个的.


3) 2-way associative
   是fully associative和Direct Mapped Cache的折中.
   2-way, 每一个人可以有两个停车位, 这样当一个停车位被占了的时候, 还有机会寻找另外一个. 虽然人数众多, 但同时来找停车位的人并不多. (相当于很多人的车在外面,没有开回来)
   所以, 2-way associative近似的相当于有了2倍大小的cache, 使用Direct Mapped Cache策略.

cache-hash.jpg
注意, 这图只统计了cache miss率, 很显然full-associative是做好的. 但是full-associative导致的判断一个地址是否在cache中的代价是非常昂贵的.所以, 生产环境一般都是2-way associative
======================================================

多线程变成中避免以及识别错误的共享变量方式 主要解决在SMP环境下cache line被频繁刷新的的问题
Avoiding and Identifying False Sharing Among Threads

举例:
// 如下代码在SMP环境下存在cache频繁刷新问题
double sum=0.0, sum_local[NUM_THREADS];
#pragma omp parallel num_threads(NUM_THREADS)
{
 int me = omp_get_thread_num();
 sum_local[me] = 0.0;

 #pragma omp for
 for (i = 0; i < N; i++)
 sum_local[me] += x[i] * y[i];

 #pragma omp atomic
 sum += sum_local[me];
}
因为sum_local数组是个全局变量, 多个线程都会访问, 并且, 各个线程访问的地方很接近, 会导致一个线程更新, 其他CPU的cache line失效.
smp.jpg

解决该问题的方法是
1) 不同线程之间尽量少的访问全局变量, 尽量使用线程局部变量.
2) 如果一定要访问, 尽量让各个线程自己访问的区域cacheline对齐.
3) 频繁更新的存储和不频繁更新的存储分开.


slab 着色的理解

之前对slab着色一直不怎么理解。
后来查了不少资料,包括之前转的一篇blog,通过学习之后,总算大致了解了。

这儿也写点总结,按照自己的思路来,以抵抗忘性。

先来看看slab着色的目的。
slab中倾向于把大小相同的对象放在同一个硬件cache line中。为什么呢?方便对齐,方便寻址。
但这样会带来一个问题。
假如有两个对象,A,B,它们size一样,都是18个字节。
这样,如果交替访问这两个对象时,就会造成这两个对象不停地从cache line中换入/换出到RAM中,而其他的cache line很有可能闲着没事干。
怎么解决这个问题呢?
slab着色就上场了。

slab着色是怎么回事呢?
其实就是利用slab中的空余空间去做不同的偏移,这样就可以根据不同的偏移来区分size相同的对象了。
为什么slab中会有剩余空间?因为slab是以空间换时间。
做偏移的时候,也要考虑到cache line中的对齐。
假如slab中有14个字节的剩余空间,cache line以4字节对齐,我们来看看有多少种可能的偏移,也就是有多少种可能的颜色。
第一种,偏移为0,也就是不偏移,那么剩余的14个字节在哪儿呢?放到结尾处,作为偏移补偿。
第二种,偏移4字节,此时偏移补偿为10字节。
第三种,偏移8字节,此时偏移补偿为6字节。
第四种,偏移12字节,此时偏移补偿为2字节。
再继续,就只能回归不偏移了,因为上一种的偏移补偿为2字节,已经不够对齐用了。
来总结一下看看有几种颜色。
第一种无偏移,后面是剩余空间 free 能满足多少次对齐 align ,就有多少种,总数: free/align +1 。
如果 free 小于 align ,slab着色就起不到作用,因为颜色只有一种,即不偏移的情况。
如果size相同的对象很多,但 free 不够大,或者 free/align 不够大,效果也不好。
因为颜色用完了,会从头再来。
继续上面的例子,如果有五个相同的对象,第五个对象的颜色与第一个相同,偏移为0.

原来着色就是这么回事!
被这个绚丽的名字唬住了好久。

http://blog.csdn.net/njuitjf/article/details/18314807


学习LKD的时候,在内存管理一章的slab小节中,对于slab的着色只是一笔带过,并没有详细叙述,只好翻看了很多资料,稍微有了点儿概念,其实关键在于分清所谓的cache(高速缓存,包含多个slab块)和硬件高速缓存的概念。
        slab的设计原理和主体代码不难理解,相应的内存管理效率提升原理也不难理解,问题在于slab着色的原理和用途。我们都知道slab中,相同大小的对象倾向于存放在硬件高速缓存内部相同的cache line中,由此产生的问题是,不同slab中,相同大小的对象很可能最终映射到相同的cache line中,当进行针对这两个对象的读操作时,就出现了两个对象在cache line和RAM之间来回不停切换的现象,更糟糕的是,剩下的一些cache line可能正在无所事事,着色的主要目的就是避免类似的现象发生。
     由于slab是采用空间换时间的方式提高分配效率,因此在slab块中会存在没有用处的字节,我们称作free,另外,由于要和硬件缓存内部的cache line对齐,还存在一个对齐因子的概念,称作aln,能够使用的颜色数据为free / aln+1,free个无用字节被划分为着色域和着色补偿域两部分,分别位于slab块的头和尾,着色域后面紧跟着slab描述符+对象描述符,接下来就是每个对象了,对于对象大小相同的不同slab块,着色域和着色补偿域的大小分别为col x aln和free-col x aln,其中col表示当前slab块的颜色数,当col x aln=free时,代表颜色数已经分配完,新的对象大小相同的slab生成时,col数目从0开始新一轮的循环。这样,就使得对象大小相同的不同slab块中的对象拥有不同的位移量,确保它们不会被映射到硬件缓存内部相同的cache line中。以上是理想情况,当free < aln时,颜色数只有1个,依然无法避免冲突。
     小小总结一下,slab着色功能的高效性是建立在颜色数很多的情况下,但细心的人也会想到,当对象大小相同的slab数目远远超过颜色数时,着色仍然避免不了出现冲突。基于这一点考虑,再加上slab的复杂结构和诸如缓存队列等复杂的层次结构带来的高内存占用,Linux内核小组在2.6.23以及之后的内核版本中,采用slub算法替代了slab算法,按照Linux内核小组人员的说法,slub较slab提升了5%~10%的性能并减少了50%的内核缓存占用,也就是说,不仅仅从时间,而且从空间上都较slab算法有了改善,slub完全兼容slab的接口,内核的其他模块无需修改代码就可以从新的内核缓存分配算法中收益。

 


     同一硬件高速缓存行可以映射 RAM 中多个不同的块,相同大小的对象倾向于存 放在高速缓存内相同的偏移量处。在不同 slab 内具有相同偏移量的对象最终很可能映射到 同一高速缓存行中。而使用 slab 分配器的对象通常是频繁使用的小对象,高速 缓存的硬件可能因此而花费内存周期在同一高速缓存行与 RAM 内存单元之间来来往往的传送两个对象。

 

如下例:假设 cache 行为 32Bytes , CPU 包含 512 个 cache 行(缓存大小 16K )。

 

     假设对象 A,B 均为 32B ,且 A 的地址从 0 开始, B 的地址从 16K 开始,则根据组相联或直接相联映射方式 (全相联方式很少使用), A,B 对象很可能映射到 cache 的第 0 行 ,此时,如果 CPU 交替的访问 A,B 各 50 次,每一次访问 cache 第 0 行都失效,从而需要从内存传送数据。而 slab 着色就是为解决该问题产生的,不同的颜色 代表了不同的起始对象偏移量,对于 B 对象,如果将其位置偏移向右偏移 32B ,则其可能会被映射到 cache 的第 1 行上,这样交替的访问 A,B 各 50 次,只需要 2 次内存访问即可。

     这里的偏移量就代表了 slab 着色中的一种颜色,不同的颜色代表了不同 的偏移量,尽量使得不同的对象的对应到不同的硬件高速缓存行上,以最大限度的提高效率。实际的情况比上面的例子要复杂得多, slab 的着色还要考虑内存对齐等因素,以及 slab内未用字节的大小,只有当未用字节数足够 大时,着色才起作用。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章