首页 > 代码库 > Cache 总结

Cache 总结

        这一文,让我们分析一下,《浅谈 Cache》 一文中的奇怪现象,其实现在来看也并不奇怪了。
        在什么情况下 r1 和 r2 都为 0 呢?
        仔细看代码,你会发现,两个线程分别被运行在不同的 CPU 核上,并且在线程开始的阶段还使用了一个随机数,是为了让两个线程能尽量同时运行。
        假设 CPU0 运行:
        x = 1;
        r1 = y;
        CPU1 运行:
        y = 1;
        r2 = x;
        如果下面的情况发生:
        x 在 CPU1 的 Cache 中。 y 在 CPU0 的 Cache 中。
        1. CPU0 执行 x = 1, cache miss, 发送 "read invalidate" 消息,并把 x 的值 1 存入 Store Buffer, 开始执行 r1 = y, Cache 命中,r1 为 0;
        2. CPU1 执行 y = 1,cache miss, 发送 "read invalidate" 消息,并把 y 的值 1 存入 Store Buffer, 开始执行 r2 = x,Cache 命中,r2 为 0;
        3. CPU0 和 CPU1 各自收到了对方的消息,并作出回应,x 和 y 的值均应用到 Cache 中,且都为 1;
        主函数收到信号,比较 r1 和 r2 的值,奇迹发生了。
        如果你知道我讲的这些细节,就会发现,其实这并不是奇怪了。那么如果解决这个问题呢?
        其实答案就很简单了,要么让两个线程运行在同一个核心上,要么在两个语句之间加上内存屏障,经验证,问题解决了。
        题外篇:
        在不同 CPU 架构下,对内存的乱序访问其实是不同的,一般的内存乱序分为以下四种:
        LoadStore, LoadLoad, StoreStore, StoreLoad。并且 X86 下只会出现 StoreLoad 乱序,也就是上面的例子,我的 CPU 是 X86 的,所以出现了这种情况,由此可知,其实 X86 内存乱序访问的还不算太厉害。
        简单解释一下,x = 1,为 Store, 读取 y 的过程为 Load,所以 Load 指令在 X86 下允许在 Store 还未更新到 Cache 中之前被执行。
        走出一个误区,内存乱序访问与 CPU 乱序执行(Out of Order,即 OOO)不同。
早期的处理器为有序处理器(In-order processors),有序处理器处理指令通常有以下几步:
        1. 指令获取

        2. 如果指令的输入操作对象(input operands)可用(例如已经在寄存器中了),则将此指令分发到适当的功能单元中。如果一个或者多个操作对象不可用(通常是由于需要从内存中获取),则处理器会等待直到它们可用;

        3. 指令被适当的功能单元执行

        4. 功能单元将结果写回寄存器堆(Register file,一个 CPU 中的一组寄存器)
 
        相比之下,乱序处理器(Out-of-order processors)处理指令通常有以下几步:
        1. 指令获取
        2. 指令被分发到指令队列
        3. 指令在指令队列中等待,直到输入操作对象可用(一旦输入操作对象可用,指令就可以离开队列,即便更早的指令未被执行)
        4. 指令被分配到适当的功能单元并执行
        5. 执行结果被放入队列(而不立即写入寄存器堆)
        只有所有更早请求执行的指令的执行结果被写入寄存器堆后,指令执行的结果才被写入寄存器堆(执行结果重排序,让执行看起来是有序的)
        从上面的执行过程可以看出,乱序执行相比有序执行能够避免等待不可用的操作对象(有序执行的第二步)从而提高了效率。现代的机器上,处理器运行的速度比内存快很多,有序处理器花在等待可用数据的时间里已经可以处理大量指令了。
        现在思考一下乱序处理器处理指令的过程,我们能得到几个结论:
        对于单个 CPU 指令获取是有序的(通过队列实现)
        对于单个 CPU 指令执行结果也是有序返回寄存器堆的(通过队列实现)
        由此可知,在单 CPU 上,不考虑编译器优化导致乱序的前提下,多线程执行不存在内存乱序访问的问题
        CPU 虽然是乱序执行,但是是顺序流出结果,在我们看来,乱序执行对我们来讲是透明的,我们看到的结果和指令顺序是一样的。

Cache 总结