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

Posted

技术标签:

【中文标题】JVM 是不是有能力检测并行化机会?【英文标题】:Does the JVM have the ability to detect opportunities for parallelization?JVM 是否有能力检测并行化机会? 【发布时间】:2012-06-11 08:27:26 【问题描述】:

Java Hotspot 可以很好地优化顺序代码。但我猜测随着多核计算机的出现,运行时的信息是否可以用于检测在运行时并行化代码的机会,例如检测软件流水线是否可能在循环中和类似的事情。

在这个主题上做过任何有趣的工作吗?还是研究失败或一些难以解决的停顿问题?

【问题讨论】:

通常情况下,ExecutorService 或 fork/join 是更好的选择。鉴于在线程之间分配任务所需的时间很长,自动编译器不太可能比手写代码更好。通常对于简单的循环,使用多个线程会更慢。 现在很多其他编译器都在做这种事情,我看不出为什么 JVM/JIT 不应该这样做。 从我的实践经验来看,JVM其实很擅长这个。 【参考方案1】:

我认为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 字段也可以使用反射来更改。因此不能保证计算的输出(或输入!)不会被并行化改变 -> 所以自动并行化通常是不可行的。

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

(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!

【讨论】:

这完全忽略了(相对常见的)纯算术计算的情况,HotSpot 应该能够像其他任何人一样并行化。可以吗? @Louis:“纯算术计算”到底是什么意思?我不知道 HotSpot 是否可以这样做,但所有现代处理器都已经在使用 branch prediction 和 speculative optimization,所以我不确定 HotSpot 是否可以添加任何东西。 不是调用expensiveComputation 方法,而是直接在数组上进行数学运算的循环呢? 愚蠢的例子:一个循环将数组的每个元素加倍。 你需要 1) 一个数组,它是可变的,2) 一个 * 运算符,它是纯的,3) 一个循环,它访问该数组中的元素 并写入它们 i> 如果您正在就地修改。为简单起见,我们假设循环严格从 0 到 array.length-1,没有技巧,让我们创建一个新数组来摆脱就地修改,所以我们剩下 1)。如果数组是堆栈本地的并且没有泄漏到外部,那么很容易证明可以并行化。即使是这个“愚蠢”的例子也需要证明几件事——因为可变性——所以想象一下一般情况!

以上是关于JVM 是不是有能力检测并行化机会?的主要内容,如果未能解决你的问题,请参考以下文章

Bash脚本实现批量作业并行化

用两条语句并行化 while 循环(弗洛伊德循环检测算法)

如何在并行 JVM 进程(不是线程)中运行 TestNG 测试

使用JAVA CompletableFuture实现流水线化的并行处理,深度实践总结

“线程安全”函数是不是依赖于并行化框架?

OpenCV中聚类方法的并行化