Java Hotspot 可以很好的优化顺序代码。但我在猜测,随着多核计算机的出现,运行时的信息是否可用于检测在运行时并行化代码的机会,例如检测软件流水线在循环中是否可能以及类似的事情。
有没有关于这个主题的有趣的工作?还是研究失败或一些很难解决的问题?
最佳答案
我认为 Java memory model 的当前保证使得在编译器或 VM 级别做很多(如果有的话)自动并行化非常困难。 Java 语言没有语义来保证任何数据结构实际上是不可变的,或者任何特定的语句都是纯净的且没有副作用,因此编译器必须自动计算出这些以便并行化。一些基 native 会可能会在编译器中推断出来,但一般情况将留给运行时,因为动态加载和绑定(bind)可能会引入在编译时不存在的新突变。
考虑以下代码:
for (int i = 0; i < array.length; i++) {
array[i] = expensiveComputation(array[i]);
}
如果 expensiveComputation
是一个 pure function,并行化将是微不足道的,它的输出仅取决于它的参数,如果我们能保证 array
在循环期间不会改变(实际上我们正在改变它,设置 array[i]=.. .
,但在这种特殊情况下,总是首先调用 expensiveComputation(array[i])
,所以这里没问题 - 假设 array
是本地的,而不是从中引用的其他任何地方)。
此外,如果我们像这样改变循环:
for (int i = 0; i < array.length; i++) {
array[i] = expensiveComputation(array, i);
// expensiveComputation has the whole array at its disposal!
// It could read or write values anywhere in it!
}
那么即使 expensiveComputation
是纯粹的并且不改变它的参数,并行化也不再是微不足道的,因为并行线程会改变 的内容array
而其他人正在阅读它!并行器必须确定 哪些部分 在各种条件下 expensiveComputation
指的是数组,并相应地进行同步。
也许检测所有可能发生的突变和副作用并在并行化时将其考虑在内并非完全不可能,但非常> 很难,当然,在实践中可能不可行。这就是为什么并行化,并弄清楚一切仍然正常工作,是 Java 程序员头疼的问题。
函数式语言(例如 JVM 上的 Clojure)是该主题的热门答案。纯粹的、无副作用的功能以及 persistent (“有效不可变”)数据结构可能允许隐式或几乎隐式并行化。让我们将数组的每个元素加倍:
(map #(* 2 %) [1 2 3 4 5])
(pmap #(* 2 %) [1 2 3 4 5]) ; The same thing, done in parallel.
这是透明的,因为有两点:
#(* 2 %)
函数是纯粹的:它接受一个值并给出一个值,仅此而已。它不会改变任何东西,它的输出仅取决于它的参数。- vector
[1 2 3 4 5]
是不可变的:无论是谁在什么时候查看它,它都是一样的。
可以在 Java 中创建纯函数,但 2) 不变性是此处的致命弱点。 Java 中没有不可变的数组。 老实说,没有什么 在 Java 中是不可变的,因为即使 final
字段也可以使用反射更改。因此,无法保证计算的输出(或输入!)不会被并行化改变 -> 因此自动并行化通常是不可行的。
由于不变性,愚蠢的“加倍元素”示例扩展到任意复杂的处理:
(defn expensivefunction [v x]
(/ (reduce * v) x))
(let [v [1 2 3 4 5]]
(map (partial expensivefunction v) v)) ; pmap would work equally well here!
关于java - JVM 是否具有检测并行化机会的能力?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10929184/