JVM 是否有能力检测并行化机会?
Java Hotspot 可以很好地优化顺序代码.但我猜测随着多核计算机的出现,运行时的信息是否可以用于检测在运行时并行化代码的机会,例如检测软件流水线是否可能在循环中和类似的事情.
The Java Hotspot can optimize the sequential code very well. But I was guessing that with the advent of multi-core computers, can the information at runtime be useful to detect opportunities to parallelize the code at runtime, for example detect the software pipelining is possible in a loop and similar things.
在这个主题上做过任何有趣的工作吗?还是研究失败或一些难以解决的停顿问题?
Was any interesting work ever been done on this topic ? Or is it a research failure or some halting problem which is very hard to solve?
推荐答案
我认为 Java 的当前保证内存模型 使得在编译器或虚拟机级别上做很多(如果有的话)自动并行化非常困难.Java 语言没有语义来保证任何数据结构甚至是有效的不可变的,或者任何特定的语句是纯粹的并且没有副作用,因此编译器必须自动计算出这些以实现并行化.在编译器中可以推断出一些基本的机会,但一般情况将留给运行时,因为动态加载和绑定可能会引入编译时不存在的新突变.
I think the current guarantees of the Java memory model make it quite hard to do much, if any, automatic parallelization at the compiler or VM level. The Java language has no semantics to guarantee that any data structure is even effectively immutable, or that any particular statement is pure and free of side-effects, so the compiler would have to figure these out automatically in order to parallelize. Some elementary opportunities would be possible to infer in the compiler, but the general case would be left to the runtime, since dynamic loading and binding could introduce new mutations that didn't exist at compile-time.
考虑以下代码:
for (int i = 0; i < array.length; i++) {
array[i] = expensiveComputation(array[i]);
}
如果 expensiveComputation
是 纯函数
It would be trivial to parallelize, if expensiveComputation
is a pure function, whose output depends only on its argument, and if we could guarantee that array
wouldn't be changed during the loop (actually we're changing it, setting array[i]=...
, but in this particular case expensiveComputation(array[i])
is always called first so it's okay here - assuming that array
is local and not referenced from anywhere else).
此外,如果我们像这样改变循环:
Furthermore, if we change the loop like this:
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
是纯的并且不改变其参数,并行化也不再是微不足道的,因为并行线程会改变 的内容数组
而其他人正在阅读它!并行器必须在各种条件下找出 expensiveComputation 数组的部分,并相应地进行同步.
then parallelization is not trivial any more even if expensiveComputation
is pure and doesn't alter its argument, because the parallel threads would be changing the contents of array
while others are reading it! The parallelizer would have to figure out which parts of the array expensiveComputation
is referring to under various conditions, and synchronize accordingly.
也许检测所有可能发生的突变和副作用并在并行化时将其考虑在内并不是完全不可能,但它会非常> 很难,当然,在实践中可能是不可行的.这就是为什么并行化以及确定一切仍然正常工作是 Java 程序员最头疼的原因.
Perhaps it wouldn't be outright impossible to detect all mutations and side-effects that may be going on and take those into account when parallelizing, but it would be very hard, for sure, probably infeasible in practice. This is why parallelization, and figuring out that everything still works correctly, is the programmer's headache in Java.
函数式语言(例如 JVM 上的 Clojure)是该主题的热门答案.纯粹的、无副作用的函数与 persistent(实际上不可变")数据结构可能允许隐式或几乎隐式的并行化.让我们将数组的每个元素加倍:
Functional languages (e.g. Clojure on JVM) are a hot answer to this topic. Pure, side-effect-free functions together with persistent ("effectively immutable") data structures potentially allow implicit or almost implicit parallelization. Let's double each element of an array:
(map #(* 2 %) [1 2 3 4 5])
(pmap #(* 2 %) [1 2 3 4 5]) ; The same thing, done in parallel.
这是透明的,因为有两件事:
This is transparent because of 2 things:
#(* 2 %)
函数是纯函数:它接受一个值并给出一个值,仅此而已.它不会改变任何东西,它的输出只取决于它的参数.- 向量
[1 2 3 4 5]
是不可变的:无论谁在看,什么时候看,都是一样的.
- The function
#(* 2 %)
is pure: it takes a value in and gives a value out, and that's it. It doesn't change anything, and its output depends only on its argument. - The vector
[1 2 3 4 5]
is immutable: no matter who's looking at it, or when, it's the same.
在 Java 中可以创建纯函数,但是 2) 不变性是这里的致命弱点.Java 中没有不可变的数组. 学究起来,nothing 在 Java 中是不可变的,因为即使是 final
字段也可以使用反射来更改.因此不能保证计算的输出(或输入!)不会被并行化改变 -> 所以自动并行化通常是不可行的.
It's possible to make pure functions in Java, but 2), immutability, is the Achilles' heel here. There are no immutable arrays in Java. To be pedant, nothing is immutable in Java because even final
fields can be changed using reflection. Therefore no guarantees can be made that the output (or input!) of a computation wouldn't be changed by parallelization -> so automatic parallelization is generally infeasible.
由于不变性,愚蠢的加倍元素"示例扩展到任意复杂的处理:
The dumb "doubling elements" example extends to arbitrarily complex processing, thanks to immutability:
(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!
相关文章