Компиляторы как Javac автоматически обнаруживают чистые функции и распараллеливают их?


12

Известно, что чистые функции облегчают парелелизацию. Что такого в функциональном программировании, которое делает его по сути адаптированным для параллельного выполнения?

Являются ли такие компиляторы, как Javac, достаточно умными, чтобы определить, когда метод является чистой функцией? Всегда можно реализовать классы, которые реализуют функциональные интерфейсы, такие как Function , но имеют побочные эффекты.


7
Вопрос не только в том, может ли компилятор знать, является ли функция чистой, но также и в том, может ли он разумно планировать параллельное выполнение чистых функций. Недостаточно запустить новый поток для каждого: это неэффективно. GHC (Haskell) справляется с этим, используя лень и «зеленые нити»; Честно говоря, я был бы удивлен, если бы даже попытался использовать какой-нибудь нечистый язык, учитывая дополнительную сложность убедиться, что чистые потоки были правильно спланированы относительно основного нечистого потока.
Райан Райх

@RyanReich, есть ли прирост производительности при использовании функционального программирования на нечистом функциональном языке, таком как Java? достижения функционального программирования являются чисто функциональными, такими как модульность?
Навин

@RyanReich GHC решает эту проблему, когда программист комментирует, когда им нужен параллелизм. Чистота подразумевает, что эти аннотации никогда не меняют семантику, только производительность. (Существуют также механизмы параллелизма, которые могут привести к параллелизму, но это другой котелок рыбы.)
Дерек Элкинс покинул SE

@Naveen Есть и другие преимущества для чистых функций в отношении оптимизации, помимо параллелизма, такие как большая свобода переупорядочивания кода, запоминание и устранение общих подвыражений. Я могу ошибаться, но я сомневаюсь, что javac пытается обнаружить чистоту, хотя, вероятно, это довольно редко в идиоматическом коде и несколько сложно для всех, кроме самых тривиальных случаев. Например, вам нужно знать, что не будет никаких NullPointerExceptions. Преимущества оптимизаций, основанных на этом, также, вероятно, довольно малы для типичных приложений Java.
Дерек Элкинс покинул SE

6
javac - это компилятор java, который берет исходный код java и генерирует файлы классов байт-кода java. Это довольно ограничено относительно того, что он может (и должен) делать. Он не имеет свободы или необходимых базовых механизмов для введения параллелизма в файл класса байт-кода.
Эрик Эйдт

Ответы:


33

являются ли такие компиляторы, как Javac, достаточно умными, чтобы определять, когда метод является чистой функцией.

Это не вопрос "достаточно умен". Это называется анализом чистоты и в общем случае доказуемо невозможно: это эквивалентно решению проблемы остановки.

Теперь, конечно, оптимизаторы все время делают доказуемо невозможные вещи, «доказуемо невозможные в общем случае» не означает, что они никогда не работают, это лишь означает, что они не могут работать во всех случаях. Итак, на самом деле существуют алгоритмы для проверки, является ли функция чистой или нет, просто чаще всего результатом будет «Я не знаю», что означает, что по соображениям безопасности и правильности, вы должны предположить, что эта конкретная функция может быть нечистой.

И даже в тех случаях , когда он делает работу, алгоритмы являются сложными и дорогостоящими.

Итак, это проблема № 1: она работает только для особых случаев .

Проблема № 2: Библиотеки . Чтобы функция была чистой, она может вызывать только чистые функции (и эти функции могут вызывать только чистые функции и т. Д. И т. Д.). Javac, очевидно, знает только о Java, и он знает только о коде, который он может видеть. Таким образом, если ваша функция вызывает функцию в другом модуле компиляции, вы не можете знать, является ли она чистой или нет. Если он вызывает функцию, написанную на другом языке, вы не можете знать. Если это вызывает функцию в библиотеке, которая может даже не быть установлена, вы не можете знать. И так далее.

Это работает, только если у вас есть анализ всей программы, когда вся программа написана на одном языке, и все компилируется сразу за один раз. Вы не можете использовать любые библиотеки.

Проблема № 3: Планирование . После того, как вы выяснили, какие части являются чистыми, вам все равно нужно запланировать их для разделения потоков. Или не. Запуск и остановка потоков очень дороги (особенно в Java). Даже если вы сохраняете пул потоков и не запускаете и не останавливаете их, переключение контекста потоков также является дорогостоящим. Вы должны быть уверены, что вычисления будут выполняться значительно дольше, чем требуется для планирования и переключения контекста, иначе вы потеряете производительность, а не увеличите ее.

Как вы, наверное, уже догадались, вычисление того, сколько времени займет вычисление, в общем случае невозможно доказать (мы даже не можем понять, займет ли это конечное количество времени, не говоря уже о том, сколько времени), а также трудно и дорого даже в особый случай.

В стороне: Javac и оптимизации . Обратите внимание, что большинство реализаций javac на самом деле не выполняют много оптимизаций. Например, реализация Oracle Javac опирается на базовый механизм выполнения для оптимизации . Это приводит к другому набору проблем: скажем, javac решил, что определенная функция является чистой и достаточно дорогой, и поэтому он компилирует ее для выполнения в другом потоке. Затем появляется оптимизатор платформы (например, JIT-компилятор HotSpot C2), который оптимизирует всю функцию. Теперь у вас есть пустой поток, ничего не делая. Или, представьте, опять же, javac решает запланировать функцию в другом потоке, и оптимизатор платформы может полностью оптимизировать его, за исключением того, что он не может выполнять встраивание через границы потоков, поэтому функция, которая может быть полностью оптимизирована, теперь выполняется без необходимости.

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

Следует отметить , что, например, компилятор HotSpot С2 JIT фактически делает выполнять некоторые автоматической векторизации, который также является одной из форм автоматического распараллеливания.


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

@Deduplicator Ну, в зависимости от вашего определения definition, используя разношерстную definitionиз purity, вероятно , неясное
кот

1
Ваша проблема № 2 в основном сводится на нет тем фактом, что JIT выполняет практически все оптимизации (вы, очевидно, знаете это, но игнорируете это). Точно так же проблема № 3 частично становится недействительной, поскольку JIT в значительной степени зависит от статистики, собранной интерпретатором. Я особенно не согласен с «Вы не можете использовать какие-либо библиотеки», так как на помощь приходит деоптимизация. Я согласен, что дополнительная сложность будет проблемой.
Маартин

2
@maaartinus: Кроме того, только самый конец моего ответа относится к javac. Я, в частности , упоминаю, например, что «Это работает только тогда, когда у вас есть анализ всей программы, когда вся программа написана на одном языке и все компилируется сразу за один раз». Это очевидно верно для C2: он имеет дело только с одним языком (байт-кодом JVM) и имеет доступ ко всей программе одновременно.
Йорг Миттаг

1
@ JörgWMittag Я знаю, что ОП спрашивает о javac, но держу пари, что они предполагают, что именно javac отвечает за оптимизацию. И что они вряд ли знают, что есть C2. Я не говорю, ваш ответ плохой. Просто позволить javac выполнять какую-либо оптимизацию (за исключением таких мелочей, как использование StringBuilder), не имеет смысла, поэтому я бы отклонил это и просто предположил, что OP пишет javac, но означает Hotspot. Ваша проблема № 2 является довольно веской причиной против оптимизации чего-либо в javac.
Маартин

5

В этом ответе не было замечено ничего. Синхронное взаимодействие между потоками чрезвычайно дорого. Если функция может выполняться с частотой в несколько миллионов вызовов в секунду, на самом деле вам будет больно распараллеливать ее, а не оставлять ее как есть.

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

Таким образом, компилятор должен не только определить, является ли функция чистой, он также должен оценить время выполнения функции, чтобы увидеть, является ли распараллеливание чистым выигрышем. Кроме того, нужно будет выбирать между занятыми циклами, используя атомарные переменные или условные переменные. И это должно было бы создать темы за вашей спиной.

Если вы создаете потоки динамически, это даже медленнее, чем использование условных переменных. Таким образом, компилятору необходимо настроить определенное количество уже запущенных потоков.

Итак, ответ на ваш вопрос - нет , компиляторы недостаточно умны, чтобы автоматически распараллеливать чистые функции, особенно в мире Java. Они умны, не распараллеливая их автоматически!


5
« Они умны, не распараллеливая их автоматически! » : Это заходит слишком далеко. Хотя это правда, что распараллеливание в любой возможной точке просто ради себя, как правило, будет неэффективным, умный компилятор определит практическую стратегию распараллеливания. Я думаю, что большинство людей это понимают, поэтому, когда мы говорим об автопараллелизации, мы имеем в виду автопрактикулизацию.
Нат

@Nat: Смешно слишком сложно. Это потребует идентификации чистых функций в масштабе времени выполнения, составляющих 100 с, и миллисекунд, и ожидать, что компилятор получит представление о времени выполнения циклов, которые не имеют констант в своих итерациях (и тех случаях, которые вы не хотите), глупо.
Джошуа

Я согласен - @ Nat комментарий подразумевает, что распараллеливание не обязательно означает несколько потоков, что верно. JIT может, например, встроить несколько вызовов в чистую функцию и чередовать их инструкции CPU в определенных случаях. Например, если оба вызова метода извлекают константу, она может быть выбрана один раз и сохранена в регистре ЦП для использования обоими экземплярами метода. Современные процессоры - это звери с многочисленными регистрами общего назначения и специальными инструкциями, которые могут быть весьма полезны при оптимизации кода.

1
@ Joshua: Гораздо проще для JIT-компилятора. JIT-компилятор также может выяснить, что функция может быть не чистой, но до сих пор ни один вызов не вызывал нечистое поведение.
gnasher729

Я согласен с @ Джошуа. У меня есть трудный для распараллеливания алгоритм на работе. Я попытался вручную распараллелить его, даже выполнив несколько упрощающих приближений (и, таким образом, изменив алгоритм), и каждый раз терпел неудачу. Даже программа, показывающая, выполнимо ли распараллелить что-либо, чрезвычайно сложна, хотя это будет гораздо проще, чем распараллеливание. Помните, что мы говорим о Turing-complete языках программирования.
Юлист
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.