JVM是否有能力检测并行化的机会?

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的内容而其他人正在读取它! 并行化程序必须确定在各种条件下costComputation引用的数组的哪些部分 ,并相应地进行同步。

也许完全不可能检测到可能发生的所有突变和副作用,并在进行并行化时将其考虑在内,但实际上这可能是不可行的。 这就是为什么并行化,并确定一切仍然正常,是程序员在Java中头痛的原因。

函数式语言(例如JVM上的Clojure)是本主题的热门答案。 纯粹的无副作用函数与持久 (“有效不可变”)数据结构一起可能允许隐式或几乎隐式并行化。 让我们将数组的每个元素加倍:

 (map #(* 2 %) [1 2 3 4 5]) (pmap #(* 2 %) [1 2 3 4 5]) ; The same thing, done in parallel. 

这是透明的,因为有两件事:

  1. 函数#(* 2 %)是纯粹的:它取一个值并给出一个值,就是这样。 它不会改变任何东西,它的输出仅取决于它的参数。
  2. 向量[1 2 3 4 5]是不可变的:无论是谁在看它,或者何时,它都是一样的。

可以用Java创建纯函数,但2),不变性,这里是阿基里斯的脚跟。 Java中没有不可变数组。 作为一个学者,Java中没有任何东西是不可变的,因为即使是final字段也可以使用reflection来改变。 因此,不能保证计算的输出(或输入!)不会因并行化而改变 - >因此自动并行化通常是不可行的。

由于不变性,愚蠢的“倍增元素”示例扩展到任意复杂的处理:

 (defn expensivefunction [vx] (/ (reduce * v) x)) (let [v [1 2 3 4 5]] (map (partial expensivefunction v) v)) ; pmap would work equally well here!