slab着色–一种必然认输的妥协

在最新的linux2.6.28内核当中已经不见slab着色的踪迹了,记得研究2.6.9的时候,我还为理解slab着色大伤脑筋,而实际上我当时根本没有理解它的设计初衷以及最终的效果,只是把它当成了一个纯粹的“算法”来玩,还玩得不亦乐乎...
      slab的目的是什么?其实它是为了在软件缓存和硬件缓存冲突的时候达成的一种妥协,而普遍的观点是只有相互平等的当事者才具有妥协的可能,如果关系根本 tmd就不平等,那么妥协只是可怜的一厢情愿而已。那么硬件和软件平等吗?在高处看的话是平等的,我们常说硬件的功能完全可以用软件模拟,想象一下,一个cpu 就可以模拟很多的专业电路,靠的就是cpu执行的软件指令,内部是布尔逻辑解决的,诸多的布尔逻辑组成了复杂的算法,完全模拟了专业的硬件连线逻辑,但是 我们不从功能上谈,从别的方面想想,它们就不那么平等了,首先它们的层次不同,计算机硬件往往只是提供了一个平台,一个场地,它提供的是彼此正交的指令系 统,具体怎么发挥要看软件的算法设计,可以说硬件提供了机制而软件实现了策略,如果所有的策略都由硬件直接实现,那么就回到了工业革命或者电气革命时代, 软件工业就消失了,可现实是软件发展的如火如荼,现在硬件发展的已经相当成熟了(摩尔定律制约),我们说一旦出了问题,先不要想是不是机制有问题,而是先 想想是不是我的策略没有用好机制,就好像你在linux上写了一个程序,结果出了些问题,那么不要一开始就怀疑内核出了问题,当然问题的修正,不管是本质 问题还是效率问题都应该应用程序来修正而不能为了迎合一个应用程序的策略而大修改内核机制。同样的道理,当slab代表的软件缓存和cpu硬件cache 有冲突的时候,那么需要修正的也是软件slab缓存。在具体分析之前我为了节省脑细胞(呵呵,主要是我的文笔太差,想表达一个意思对我来说很有难度,因此 我老婆总说读我的东西总像在读翻译得很糟糕的中译本)我先引用一段:
另一个slab allocator注意到的问题是cpu cache的使用率.一般的cache算法是
           cache location = address % cache_size
一 般的power of two配置法配置的内存都会经过align(对齐)(首次模仿袁老), 并且大多数程式的习惯会把最常用的资料栏位放在一个结构的最前面. 这两个效应合在一起, 造成这些栏位互相的清掉彼此的cache. 512kb的cache可能只有部分有作用. 更甚者, 如果主记忆体使用interleave的方式, 比如说SPARC center 2000 使用两个bus, 较低的256byte使用第一个bus,较高的256byte使用第二个bus, 那麽所有的data可能会集中在第一个bus上, 造成不平衡现象.
     Slab的解决方法是在向paging系统取得一块block之後, (假设为1KB), Slab把他要用的资料摆在这个block最後面, 假设占y bytes. 假设所要配置的是inode, 大小跟前面Mach的例子一样皆是104. 那麽这块记 忆体可以提供(1024-y)/104个inode. 并且有一些馀数, 也就是剩下一些多馀的记忆体.Slab善用这些记忆体, 将之二等分, 一份摆在这块记忆体的最前面,一块摆在最後面. 最前面那块称为coloring area. Slab设法在每次配置的page上使用不同大小的coloring area, 以有效的 分散资料map到cache中的位置,增加cache rate.
(**)Allocator Footprint指的是Allocator在配置记忆体的时候将自己,以及所参考到的资料写到cpu cache/ TLB (translation lookaside buffer), 在cache/TLB上面产生的"脚印". Allocator在cache/TLB内 所留下的资料基本上是没有用的, 并且妨碍真正有用的资料留在cache上. buddy演算法需要参考许多资料才能配置记忆体, 会产生大量的"footprint", 导致cache miss增加. McKusick-Karels和zone allocator的足迹皆很小, 原因是配置记忆体的时候直接从free list上把第一个element抓出来而已. 所以一个好的配置法应该使用简单的演算来配置 物件.Slab也是使用相同的原则, 不论是配置或者是释放,都是简单的一两行运算而已,所以foot print也很小.
原文是台湾的繁体中文,而且用不同的表达词汇,我就不翻译了,大致可以看懂(我们都是炎黄子孙)。slab是好的,因为提前缓存了初始化好的频繁被用到的 结构体,但是由于对齐问题而会造成cpu的cache频频失效,那么软件的解决方法就是slab着色,注意,cpu的cache频频失效就是所谓的冲突, 冲突发生了,软件解决之,不能让cpu cache进行改进,因为那是机制,软件才是策略,于是slab着色由运而生。slab着色的本质就是让slab缓存的对象在cpu的cache里面彼此 错开,这样就不会造成cpu cache频频失效了,否则,经过对齐的很多slab对象都会map到cpu cache的相同位置,这实在不是我们想看到的,cpu cache失效带来的损失抵消了一部分slab缓存带来的性能提高。以上引用的仅仅是从原理上描述的,下面我列举出linux内核中的实现代码:
struct slab {
         struct list_head list;
         unsigned long colouroff; //该slab的第一个对象的偏移,这个偏移实现了slab对象的错位
         void *s_mem;             //对象的内存地址,需要对象的时候从这里分配。
         unsigned int inuse;      //slab中的活动对象数目。
         kmem_bufctl_t free;
         unsigned short nodeid;
};
struct kmem_cache * kmem_cache_create (const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void*, struct kmem_cache *, unsigned long), void (*dtor)(void*, struct kmem_cache *, unsigned long))
{
         size_t left_over, slab_size, ralign;
         struct kmem_cache *cachep = NULL;
         struct list_head *p;
... 
         cachep = kmem_cache_zalloc(&cache_cache, SLAB_KERNEL);
         size = ALIGN(size, align);
         left_over = calculate_slab_order(cachep, size, align, flags); //这个left_over就是上述引用的文字中的“多馀的记忆体”
...
         slab_size = ALIGN(cachep->num * sizeof(kmem_bufctl_t) + sizeof(struct slab), align);
...
         cachep->colour_off = cache_line_size(); //偏移就是cpu cache line的大小
...
         cachep->colour = left_over / cachep->colour_off; //颜色的大小就是能错位的最大的数目,比如剩余的内存大小是20,cpu缓存行的大小是4,那么最多可以偏移到5个单位,一个单位是20/5=4
...
}
static int cache_grow(struct kmem_cache *cachep, gfp_t flags, int nodeid)
{
         struct slab *slabp;
         void *objp;
         size_t offset;
...
         struct kmem_list3 *l3;
...
         offset = l3->colour_next;
         l3->colour_next++;  //colour_next字段代表的就是当前偏移到第几个单位了
         if (l3->colour_next >= cachep->colour) //当偏移的单位超过了总的最大的偏移单位数目,那么当前偏移单位值回归为0。
                 l3->colour_next = 0;
         spin_unlock(&l3->list_lock);
         offset *= cachep->colour_off; //当前要分配的slab的对象要从offset开始,这个offset和上一个slab错开了l3->colour_next和单位,每个单位的大小为colour_off
...
         objp = kmem_getpages(cachep, flags, nodeid);
...
         slabp = alloc_slabmgmt(cachep, objp, offset, local_flags, nodeid); //分配slab
...
         list_add_tail(&slabp->list, &(l3->slabs_free));
...
}
static struct slab *alloc_slabmgmt(struct kmem_cache *cachep, void *objp, int colour_off, gfp_t
local_flags, int nodeid)
{
         struct slab *slabp;
...
         slabp->inuse = 0;
         slabp->colouroff = colour_off;  //初始化偏移
         slabp->s_mem = objp + colour_off; //该slab的对象地址从当前偏移处开始,这里可以看出这个slab的对象和上一个错开了colour_off大小,这样可能就在cpu的cache里 面错开了,但是大多数情况效果甚微
...
}
看到最后一个函数alloc_slabmgmt的最后一段注释,说效果甚微,这是为什 么呢?这其实就是最终撤销slab着色的原因之一。我们看到如果slab的数目只有cachep->colour个的话,这个slab着色的效果就 太好了,但是这往往不太现实,slab的数目有时是相当大的,这样的话,slab着色实际上只是帮了一点点小忙而已,它仅仅保证了最开始的几个slab不 会map到同一个cpu cache line,但是待slab逐渐增加以后,后面的slab将还是会无情的打仗,从而造成cpu访问cache频频失效,这种能救几个算几个的思想可能对于人 类救灾是有效的,毕竟生命高于一切(当然不包括三氯氰胺事件),但是对于系统设计,这种效果的机制不如不要,因为我们用大量的代码维持了一个效果甚微的方 案,这是不值得的,软件设计就是这样,每笔账都要算清,赔本的生意绝对不做,内核开发者的慧眼识别出了这个滥竽充数的所谓的巧妙算法,绝然地移除了它,在 分配器从slab发展到slub以后,这个问题相对减轻了许多,slub的思想就是简单,不要那么多花里胡哨的算法,就是简单,简单就是美,这确实是一句 真理,冲突就冲突呗,只要我们带来的益处超过了冲突带来的麻烦,这就是值得的,鸵鸟算法在这种情况下就是有效的,确实是这样。上述引用的**段其实表明了 这一思想,可以好好体会一下。
作任何事情都是这样,巧妙如果变成了花拳绣腿,那它除了表演就没有别的价值了,毕竟你要维护这种巧妙需要的心血是很大的,有时可能远远大于它带来的回报。回归本真是最好的方式,简单,简单,简单就是一切!

原文链接: https://blog.csdn.net/dog250/article/details/5303421

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    slab着色--一种必然认输的妥协

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/410278

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午11:55
下一篇 2023年4月26日 上午11:55

相关推荐