Java Hotspot可以很好地优化顺序代码。但我猜测,随着多核计算机的出现,运行时的信息可以用于检测在运行时并行化代码的机会,例如检测软件流水线是否可能在循环和类似的事情中。
有关这个主题的任何有趣的工作吗?或者是研究失败还是一些很难解决的停滞问题?
答案 0 :(得分:14)
我认为Java memory model的当前保证使编译器或VM级别的自动并行化很难(如果有的话)。 Java语言没有语义来保证任何数据结构甚至是有效的不可变的,或者任何特定的语句都是纯粹的并且没有副作用,因此编译器必须自动计算这些以便并行化。在编译器中可以推断出一些基本的机会,但是一般情况将留给运行时,因为动态加载和绑定可能会引入在编译时不存在的新突变。
请考虑以下代码:
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 %)
是纯粹的:它接受一个值并给出一个值,就是这样。它不会改变任何东西,它的输出仅取决于它的参数。[1 2 3 4 5]
是不可变的:无论是谁在看它,或者何时,它都是一样的。可以用Java制作纯函数,但是2)不变性,这是阿基里斯的脚跟。 Java中没有不可变数组。要成为学究者, nothing 在Java中是不可变的,因为即使final
字段也可以使用反射进行更改。因此,不能保证计算的输出(或输入!)不会通过并行化改变 - &gt;所以自动并行化通常是不可行的。
由于不变性,愚蠢的“倍增元素”示例延伸到任意复杂的处理:
(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!