Java Hotspot 可以很好地优化顺序代码。但我猜测随着多核计算机的出现,运行时的信息是否可以用于检测在运行时并行化代码的机会,例如检测软件流水线是否可能在循环和类似的事情中。
在这个主题上做过任何有趣的工作吗?还是研究失败或一些难以解决的停滞问题?
Java Hotspot 可以很好地优化顺序代码。但我猜测随着多核计算机的出现,运行时的信息是否可以用于检测在运行时并行化代码的机会,例如检测软件流水线是否可能在循环和类似的事情中。
在这个主题上做过任何有趣的工作吗?还是研究失败或一些难以解决的停滞问题?
我认为Java 内存模型的当前保证使得很难在编译器或 VM 级别上做很多(如果有的话)自动并行化。Java 语言没有语义来保证任何数据结构甚至是有效的不可变的,或者任何特定的语句都是纯粹的并且没有副作用,因此编译器必须自动计算出这些以实现并行化。在编译器中可以推断出一些基本的机会,但一般情况将留给运行时,因为动态加载和绑定可能会引入编译时不存在的新突变。
考虑以下代码:
for (int i = 0; i < array.length; i++) {
array[i] = expensiveComputation(array[i]);
}
并行化是微不足道的,如果expensiveComputation
是一个纯函数,它的输出只取决于它的参数,如果我们可以保证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)是这个主题的热门答案。纯粹的、无副作用的函数与持久(“有效地不可变”)数据结构一起可能允许隐式或几乎隐式并行化。让我们将数组的每个元素加倍:
(map #(* 2 %) [1 2 3 4 5])
(pmap #(* 2 %) [1 2 3 4 5]) ; The same thing, done in parallel.
这是透明的,因为有两件事:
#(* 2 %)
是纯粹的:它接受一个值并给出一个值,就是这样。它不会改变任何东西,它的输出只取决于它的论点。[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!