仓库源文

.. Kenneth Lee 版权所有 2019-2020

:Authors: Kenneth Lee :Version: 1.0

对Cache Coherence的重理解


最近和一些做芯片设计的小伙伴讨论CC功能,很多地方基础语义对不齐,我用本文来理一 下概念空间。

CC本质上是人为制造出来的问题,如果你没有Cache,本来是没有CC这回事的:本来我有两 个CPU,它们分别修改内存,一个修改完了,另一个去读,就读到那个修改了,这是软件对 内存访问的一般理解:

    .. figure:: _static/cc1.jpg

然后硬件的内存做不快,它把内存分了层:

    .. figure:: _static/cc2.jpg

Cache是更快的内存,CPU大部分时候如果干自己的活,就用Cache里面的数就可以了,只有 两边要通讯的时候,才需要通知对方注意到这个“你以为的内存是假的,让我告诉你真实的 内存是啥”。

这样就有两条路线了,一条是CC,硬件自己撒的慌自己圆。硬件拍胸脯继续死撑:你用的 就是内存,你什么都不用管,就认为是内存访问就好了,如果一边修改了,另一边就会知 道。这个软件当然很喜欢,但如果你性能做不上去,你也只能打断牙往肚里吞。别出来哭 CC的代价什么的。

另一条路线是老实承认:老子这就是假的,你手上那个内存啊,其实就是给你自己的,如 果你要让其他人知道,你主动告诉他们就好了。这样,写软件的人就要麻烦很多,每次跨 核共享数据,都要主动通知对方数据修改了。

但其实从操作系统的角度,这个问题没有那么严重。因为其实操作系统设计上就认为跨核 共享是高成本的,就算不考虑你Cache不一致这个问题,计算的时候有一段数据是共享的, 根据Amdal公式,在核数增加的时候,这个性能都会急剧劣化。所以,操作系统优化多核的 基础方法就是数据Hash化,操作系统是尽量使用per_cpu数据结构的,所以你CC不CC的,很 多时候操作系统都不怎么在乎。你硬件辛辛苦苦维持住这个CC的“面子”,很可能操作系统 就用不上。

真正可能有问题的是SMP暴露给多线程库的能力。线程库告诉高层软件,我的内存都是真的 ,你多个线程修改内存,互相都能看得见。高层软件就当这个是真的了。这时没有CC,这 个故事就说不圆了。但这局限在一个进程到底有多少个核参与了。如果我一个进程只卷入 了部分的核,CC也只需要在这些核之间来保证,但还是这句话,硬件非要牛逼哄哄,说我 都能搞定,你们当做看不见就可以了,那真没有什么办法,你就继续充着呗。

但这样性能不高的时候,你就别拿CC出来说事,我们就不承认存在CC这个问题,软件按自 己逻辑优化流程,不再考虑你CC的成本问题。你也别出来跟我说这是软件的问题,软件“看 不见”!

这算是又一个“守弱”还是“守强”的问题了,如果你罩得住(CC的成本本来就不高),你说 的就是本体,这就没有“守不守弱”什么事,但如果你压力太大……“男人哭吧哭吧不是罪”……

补充1:一种看法认为,在某些实现下,CC的成本不会因为核数量的增加而增加,只和当时 共享的数据的核的数量有有关,这个逻辑可以大概抽象成这样:

    .. figure:: _static/cc3.jpg

简单说,它可以通过一张访问者记录表(据说叫Filter表),控制通知的范围,如果某个 核与写方有共享的数据,就会增加通讯成本和Latency,如果没有,就只剩下记录表的空间 成本和查表的成本。所以,芯片设计者会认为,核数增加,CC的成本上升是有限的,而共 享同一个数据的核越多,成本越高,他们认为这是你软件自找的,你们活该,他们仍认为 和他们引入Cache这件事情是无关的。

但对软件来说,更多的核体现出来的问题主要呈现为四个方面。一个是spinlock的成本, 实际的情形是共享spinlock的核越多,撞上的机会越大,同时等待CC完成的时间就越长。 你解释这是因为你共享的数据太多了,但你也提供不了更好的多核互斥的手段,这个事情 就变成“我们都没有错,千错万错都是这个世界的错”了。但无论如何吧,我们现在大概知 道这个问题的边界和出路在哪里了。

补充一句:这里说spinlock,其实是用特征代表一种类型。spinlock之外其他数据共享引 起的问题是一样的。另外,由于访问者记录表的空间是有限的,如果共享数据的范围足够 大,数据不足,原来的组播就会变成广播,这时效率仍会往下掉。

第二个问题是TLBI的问题,如果一个核修改了TLB(这常常出现在Java的JIT修改代码等清 醒里),这需要立即同步给其他同一个地址空间的其他核,如果这样的核太多,一样会严 重影响效率。这从硬件看来这就不是个CC的问题。但从部分软件工程师看来,这还是个多 核数据同步的问题。

第三个问题是Memory Barrier成本的问题,核间数据同步,加上mb才能保证共享数据的修 改顺序,更多的核形成mb更多的排队,因为根据上面的Cache目录的模型,mb的结果就是要 求更新消息保序,保序的结果就是所有更新消息必须有响应,成功以后才能进入下一步。 这会进一步劣化CC的效率。mb如果和tlb等因素结合在一起(比如修改代码后跳入执行), 引入的性能问题就会更多。

这里还是用特征代表一种类型,除了MB,其他比如原子操作,都和这个问题相关。

第四个问题是icache的问题,一种说法是:icache上一般不做filter。可能是硬件设计者 认为动态修改代码不是常态,这个问题我觉得需要的时候加上也不是什么大不了的事情吧 。

Anyway,至少我们有这么个结论:CC问题不在于核有多少(至少在现阶段,100核左右,影 响不是特别大),而在于访问相同数据块的核有多少个,只要能降低这个数量,这个问题 就不严重。

补充2:CC是总线的特性,不但可以体现在CPU上,也可以体现在设备之间,以及设备和CPU 之间。我个人大部分时候是不太理解为什么给IO设备做CC的,如果你是个加速器(比如GPU ),做CC合理,因为加速器其实就是CPU,CC能给CPU什么好处,设备也能得到什么好处。 但你说IO设备要CC做什么呢?每个数据都是只访问一次的,软件明确知道数据什么时候准 备完毕,也知道什么时候要告知IO去访问的,这些都可以主动通讯获知。

所以,这个只能解释为:在节点数不多的时候,硬件真的认为CC成本不高,做了也就做了。

补充3:基于以上结论,我们对 PopcornLinux(http://www.popcornlinux.org/images/publications/barbalace_ols.pdf) 方案做一个判断:

看起来,PopcornLinux的概念是认为大规模的SMP Linux性能是不高的,但它的测试结果其 实并没有在很多地方证明分区的多内核机制能带来比SMP Linux更好的效果。只是证明比虚 拟化有更好的效果,而且它对比的虚拟化方案并没有绑核,说起来说服力不强。

在部分情形下,Popcorn Linux确实带来一些优势,我的感觉是来自这样的执行模型:把可 以运行一组共享很多核的业务,分拆成多个子组,让部分的计算聚合在子组内,然后把其 他通讯再通过子组间的通讯进行交互,这样一定程度上降低共享数据的数量,从而提高了 效率。但这个并不需要Popcorn Linux这样的解决方案,只是需要在业务上重新组织部署就 可以了。