【问题标题】:Why are shared variables cached in CPU caches?为什么共享变量缓存在 CPU 缓存中?
【发布时间】:2018-06-06 12:29:02
【问题描述】:

我正在尝试了解 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 缓存”听起来那么简单,如果多个 CPU 共享一个缓存会怎样?而在具有多级缓存的系统中,共享变量的副本将存储在哪一级缓存中?此外,如果多个线程在同一个 CPU 内核中运行,是否意味着它们共享同一组缓存的共享变量,因此即使未定义共享变量volatile,对变量的访问也是运行在同一 CPU 上的其他线程仍然可以立即看到吗?

【问题讨论】:

  • 对于非原始类型,“变量的值” 只是 Java 中的 references-to 对象。 对象 本身不在堆栈中。字段总是与 对象 相关联,并且由于 对象 不在堆栈中。Java 内存模型对与字段读取/写入的发生关系相当宽松,即线程可见性问题经常发挥作用的地方。
  • 对于 SO 问题而言,该主题过于广泛且不具体,但您也有几个不正确的假设。 “局部变量——在线程堆栈上”:这是概念性的 Java 架构,它可能与代码在 CPU 上的实际运行方式几乎没有关系。但它很可能大部分时间都存储在主内存中,并受制于 CPU 缓存中的缓存。并且不与其他线程共享。 “共享变量——在内存中,但每个 CPU 缓存都有它的副本”——不,CPU 缓存没有那么大——它只在该 CPU 使用数据时加载数据。

标签: java multithreading visibility shared-variable


【解决方案1】:

“本地”和“共享”变量在代码上下文之外毫无意义。它们不影响缓存状态的位置或什至缓存。根据您的状态存储的位置来思考或推理甚至没有用; JMM 存在的全部原因是这样的细节不会暴露给程序员,这些细节因架构而异。通过依赖低级别的硬件细节,您对 JMM 提出了错误的问题。它对您的应用程序没有用处,它使应用程序变得脆弱、更容易被破解、更难推理并且更不便携。

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

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

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

再次,关于缓存属性的推理没有用,因为 JMM 会为您处理 ,因为它无法确定缓存的位置/时间/内容。此外,即使您确实知道何时/何地/什么问题,您仍然无法推断数据可见性;无论如何,所有缓存都以相同的方式处理缓存数据,您将需要依靠处理器更新 ME(O)SI 状态、指令排序、加载/存储缓冲、回写/直通等之间的缓存状态。 . 而且你还没有处理在操作系统和JVM级别可能出现的问题。同样,幸运的是,JDK 允许您使用基本工具,例如 volatilefinal,以及在所有平台上始终如一地工作并生成可预测且易于推理的代码。

【讨论】:

  • 你回答了我所有的问题,让我的画面非常清晰。我认为最重要的是要了解缓存更多的是一种CPU范围机制,与JVM设计无关。尽管执行一段 java 代码时 JVM 线程堆栈与 CPU 的关系对我来说不是很清楚。但我认为这是另一个过于宽泛而无法回答的问题。非常感谢。
猜你喜欢
  • 1970-01-01
  • 2017-07-15
  • 2020-11-20
  • 1970-01-01
  • 2014-07-01
  • 2011-01-04
  • 2023-03-07
  • 2018-04-24
  • 1970-01-01
相关资源
最近更新 更多