软硬件融合技术内幕 基础篇 (9) ——大厂高P毕业背后的隐情 (上)

2022-09-08 17:19:35 浏览数 (1)

在前几期,我们搞懂了计算机内存子系统如何与高速缓存配合使用,以避免速度较低的DRAM成为制约CPU运算速度的瓶颈。

前期链接:

软硬件融合技术内幕 基础篇 (6) —— 快马加鞭未下鞍

软硬件融合技术内幕 基础篇 (7) —— 倒海翻江卷巨澜

软硬件融合技术内幕 基础篇 (8) —— 刺破青天锷未残

在Intel Xeon Scalable 3代处理器中,通过对分支的预测,以及硬件预取的优化,已经将L1和L2的缓存命中率各提升到了80%。由于只有L1缓存不命中 (Cache miss)的情况下,才会去L2缓存中查找数据,而L2缓存的命中率也为80%,实际上,只有在1-(1-80%)*(1=80%) = 4%的情况下,才会出现去L3缓存中查找的情况。也就是说,大部分情况下,在程序正常运行时,缓存不命中的情况是很少见的,除非在程序中故意违反缓存友好的编程规范。

但是,在实践中,还有一类情况,一定会导致缓存不命中。这是什么情况呢?

让我们回到前几期的段子:

X姐在晋升P9以后,经常去白马女子私密会所,享受小哥哥们的巴厘岛式服务。由于时间局部性原理,X姐上一次点了6666号技师以后,下一次还点6666号技师的可能性,显著高于点其他技师的可能性。

X姐的老板,朝阳V姐,是个老P9,带X姐光顾白马女子私密会所的领路人,也因为机缘巧合,看上了6666号技师。这样一来,如果X姐和V姐同时光顾白马女子私密会所,都点了6666号技师,6666号技师就只能在服务完一位P9大佬以后,等待一个技能冷却时间,并冲洗(flush)干净,再服务另一位P9大佬。显然,这造成了顾客排队。

有没有优化技师效率,减少顾客等待时间的方法呢?

这就是这几期的主题——缓存一致性。

前面段子的实质是,在多核处理器中,如果多个CPU核都对同一个内存地址进行写操作,那么,当一个CPU核写完以后,其他CPU内部,关联这块内存的缓存就被写脏了(cacheline dirty),需要冲洗(flush)之后,才可以接待其他CPU的访问。

如图,CPU2对内存地址0x80053020进行了写操作,导致了其他CPU缓存中,映射到这一地址的缓存行标记被设定为dirty。其他CPU对该地址内存访问时,无论读写,都只能去DRAM中访问,同时刷新Cache。显然,这实质上造成了缓存行的竞争。

对这个问题的解决,有两种不同的思路:

第一种思路是,对程序的数据结构进行优化。如果多个核同时往内存中一个变量累加(如计数器)的场景,可以为每个核设立一个计数器数据结构。这样,编译器在定位全局变量的时候,会在堆(不知道什么是堆?自己去补习《编译原理》或者关注本主题后续的内容)中为每个CPU分配不同的计数器内存地址。当需要统计计数器总数的时候,将每个CPU的计数器值进行累加就行了。

如下面的代码,我们在程序中建立了一个一维数组,每个CPU读取自身的CPU id,只对计数器结构体数组g_counter中属于自己的元素进行计数。在统计总数的时候再读取g_counter全量的值并输出。

为了保证不同核的g_counter元素位于不同缓存行,我们要合理设计g_counter的数据结构,使其大小大于一个缓存行。

代码语言:javascript复制
struct counter 
{
    unsigned int counter0; /*4字节*/
    unsigned int pad[15]; /*凑齐64字节一个cacheline*/
}
g_counter[MAX_CPU];  /* 为每个CPU定义一个计数器 */

...

unsigned int process ()
{
    ....
    unsigned int cpuid = get_cpuid(); /* 取得自己的CPU ID */

    ...

    g_counter.counter0 [cpuid]   ; /* 访问属于自己CPU ID的计数器 */
    ....
}



unsigned longlong get_counter ()
{
    unsigned longlong counter = 0;

    for (int i = 0; i<MAX_CPU; i  )
    {
        counter =g_counter.counter0[i]; /* 对所有的计数器进行加总 */
    }
    return counter;
}

程序中定义的内存计数器与缓存的对应关系如上图所示,变成了一一对应,也就避免了这种多个CPU竞争同一缓存行造成的性能下降。

但是,这种方式的弊端也是显而易见的。

写出上面的程序代码,需要程序员具备针对缓存优化的意识,对人的要求很高。当出现这种原因造成的性能低于预期的问题,也难以定位和优化。

此外,这种方式,还会造成内存的一定浪费。当然,以目前内存的价格,这并不是什么很大的问题。

那么,能不能在CPU内部实现,当某个CPU核修改了内存的值的时候,同步到系统内其他CPU,使得其他CPU的缓存内容实现一致呢?

这叫做缓存一致性,也就是赋予了前面段子中,6666号技师分身的能力,让6666号技师同时服务于多个客户!

本期段子的结尾:

X姐由于与自己老板抢占技师,年度绩效虽然没有背325,但还是提前毕业了。因此,千万不要和老板抢技师(误)

0 人点赞