Подтвердить что ты не робот

Имеет ли JVM возможность обнаруживать возможности для распараллеливания?

Java Hotspot может очень хорошо оптимизировать последовательный код. Но я предполагал, что с появлением многоядерных компьютеров информация во время выполнения может быть полезна для обнаружения возможностей для параллелизации кода во время выполнения, например, обнаружение конвейеризации программного обеспечения возможно в цикле и подобных вещах.

Была ли какая-нибудь интересная работа по этой теме? Или это неудача исследования или проблема с остановкой, которую очень трудно решить?

4b9b3361

Ответ 1

Я думаю, что текущие гарантии модели памяти Java делают довольно трудным сделать много, если таковые имеются, автоматическое распараллеливание в компиляторе или виртуальной машине уровень. Язык 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.

Функциональные языки (например, Clojure на JVM) являются горячим ответом на эту тему. Чистые, свободные от побочных эффектов функции вместе с persistent ( "эффективно неизменяемые" ) структуры данных потенциально допускают неявное или почти неявное распараллеливание. Пусть двойной каждый элемент массива:

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

Это прозрачно из-за двух вещей:

  • Функция #(* 2 %) чиста: она принимает значение in и дает значение, и что оно. Это ничего не меняет, и его вывод зависит только от его аргумента.
  • Вектор [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!