14

Java Hotspot は、シーケンシャル コードを非常にうまく最適化できます。しかし、マルチコア コンピューターの出現により、実行時の情報は、実行時にコードを並列化する機会を検出するのに役立つ可能性があると推測していました。

このトピックに関して何か興味深い研究が行われたことはありますか? それとも、研究の失敗か、解決するのが非常に難しい停止中の問題ですか?

4

1 に答える 1

14

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 など) は、このトピックに対する有力な回答です。副作用のない純粋な関数と、永続的な(「事実上不変」) データ構造を組み合わせることで、暗黙的またはほぼ暗黙的な並列化が可能になる可能性があります。配列の各要素を 2 倍にしましょう。

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

これは、次の 2 つの理由から透過的です。

  1. 関数#(* 2 %)は純粋です: 値を受け取り、値を出力します。それだけです。何も変更せず、その出力は引数のみに依存します。
  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!
于 2012-06-07T10:08:00.607 に答える