为什么共享变量缓存在 CPU 缓存中?

Why are shared variables cached in CPU caches?

我正在尝试了解 Java 内存模型,但未能了解有关 CPU 缓存的要点。

据我所知,在 JVM 中,我们有以下位置来存储局部变量和共享变量:

local variables -- on thread stack

shared variables -- in memory, but every CPU cache has a copy of it

所以我的问题是:为什么将局部变量存储在堆栈上,并在 CPU 缓存中(缓存)共享变量?为什么不反过来(假设 CPU 缓存太昂贵而无法同时存储两者),我们将局部变量缓存在 CPU 缓存中并只从内存中获取共享变量?这是Java语言设计还是计算机体系结构的一部分?

进一步:听起来很简单"CPU cache",如果几个CPU共享一个缓存呢?而在多级缓存的系统中,共享变量的副本会存放在哪级缓存中呢?此外,如果在同一个 CPU 内核中 运行 有多个线程,这是否意味着它们共享同一组缓存共享变量,因此即使未定义共享变量 volatile,同一 CPU?

上的其他线程 运行 仍然可以立即看到变量的访问

"Local" 和 "shared" 变量在代码上下文之外是没有意义的。它们不会影响缓存状态的位置或状态。根据存储状态的位置来思考或推理甚至没有用; JMM 存在的全部原因是这样的细节,这些因体系结构而异的细节不会暴露给程序员。通过依赖低级硬件细节,您提出了有关 JMM 的错误问题。它对您的应用程序没有用处,它使它变得脆弱、更容易破坏、更难推理并且更难移植。

也就是说,一般来说,您应该假设任何程序状态(如果不是所有状态)都符合缓存条件。事实上,缓存什么并不重要,任何东西都可以,无论是原始类型还是引用类型,甚至是被多个字段封装的状态变量。无论线程运行什么指令(这些指令也因体系结构而异 - 请注意!),这些指令默认返回 CPU 以确定哪些与缓存相关,哪些不缓存;程序员自己不可能这样做(尽管 可能 影响 状态变量可能被缓存的位置,看看 false sharing是)。

同样,我们还可以对 x86 进行更多概括,即活动原始类型 可能 放在寄存器中,因为 P/ALUs 将能够与它们一起工作最快的。其他任何事情都会发生。如果基元是核心本地的,则有可能将基元移动到 L1/2 缓存,当然它们很可能会很快被覆盖。如果 CPU 认为将来会有上下文切换,它可能会将状态变量放在共享的 L3 上,或者它不能。硬件专家需要对此做出回应。

理想情况下,状态变量将存储在距离处理器单元最近的高速缓存(寄存器,L1/2/3,然后是主存储器)中。不过,这取决于 CPU 的决定。 不可能推断Java级别的缓存语义。即使启用了超线程(我不确定 AMD 的等价物是什么),也不允许线程共享资源,即使这样,如果它们共享,请记住可见性并不是与共享状态变量相关的唯一问题;在处理器执行流水线的情况下,您仍然需要适当的指令来确保正确的顺序(即使在您摆脱 CPU 上的 read/write 缓冲之后),这是否是 hwsync 或适当的栅栏或其他。

同样,关于缓存属性的推理没有用,因为 JMM 会为您处理 因为它是不确定的,所以 where/when/what 被缓存了。此外,即使您确实知道 where/when/what 问题,您 STILL 也无法推理数据可见性;无论如何,所有缓存都以相同的方式处理缓存数据,您将需要依靠处理器更新 ME(O)SI 状态、指令排序、load/store 缓冲、write-back/through 等之间的缓存状态...而且您还没有处理可能在 OS 和 JVM 级别发生的问题。同样,幸运的是,JDK 允许您使用基本工具,例如 volatilefinal 和跨所有平台一致工作的原子,并生成可预测且易于(呃)的代码原因.