Известно, что чистые функции облегчают парелелизацию. Что такого в функциональном программировании, которое делает его по сути адаптированным для параллельного выполнения?
Являются ли такие компиляторы, как Javac, достаточно умными, чтобы определить, когда метод является чистой функцией? Всегда можно реализовать классы, которые реализуют функциональные интерфейсы, такие как Function , но имеют побочные эффекты.
NullPointerException
s. Преимущества оптимизаций, основанных на этом, также, вероятно, довольно малы для типичных приложений Java.Ответы:
Это не вопрос "достаточно умен". Это называется анализом чистоты и в общем случае доказуемо невозможно: это эквивалентно решению проблемы остановки.
Теперь, конечно, оптимизаторы все время делают доказуемо невозможные вещи, «доказуемо невозможные в общем случае» не означает, что они никогда не работают, это лишь означает, что они не могут работать во всех случаях. Итак, на самом деле существуют алгоритмы для проверки, является ли функция чистой или нет, просто чаще всего результатом будет «Я не знаю», что означает, что по соображениям безопасности и правильности, вы должны предположить, что эта конкретная функция может быть нечистой.
И даже в тех случаях , когда он делает работу, алгоритмы являются сложными и дорогостоящими.
Итак, это проблема № 1: она работает только для особых случаев .
Проблема № 2: Библиотеки . Чтобы функция была чистой, она может вызывать только чистые функции (и эти функции могут вызывать только чистые функции и т. Д. И т. Д.). Javac, очевидно, знает только о Java, и он знает только о коде, который он может видеть. Таким образом, если ваша функция вызывает функцию в другом модуле компиляции, вы не можете знать, является ли она чистой или нет. Если он вызывает функцию, написанную на другом языке, вы не можете знать. Если это вызывает функцию в библиотеке, которая может даже не быть установлена, вы не можете знать. И так далее.
Это работает, только если у вас есть анализ всей программы, когда вся программа написана на одном языке, и все компилируется сразу за один раз. Вы не можете использовать любые библиотеки.
Проблема № 3: Планирование . После того, как вы выяснили, какие части являются чистыми, вам все равно нужно запланировать их для разделения потоков. Или не. Запуск и остановка потоков очень дороги (особенно в Java). Даже если вы сохраняете пул потоков и не запускаете и не останавливаете их, переключение контекста потоков также является дорогостоящим. Вы должны быть уверены, что вычисления будут выполняться значительно дольше, чем требуется для планирования и переключения контекста, иначе вы потеряете производительность, а не увеличите ее.
Как вы, наверное, уже догадались, вычисление того, сколько времени займет вычисление, в общем случае невозможно доказать (мы даже не можем понять, займет ли это конечное количество времени, не говоря уже о том, сколько времени), а также трудно и дорого даже в особый случай.
В стороне: Javac и оптимизации . Обратите внимание, что большинство реализаций javac на самом деле не выполняют много оптимизаций. Например, реализация Oracle Javac опирается на базовый механизм выполнения для оптимизации . Это приводит к другому набору проблем: скажем, javac решил, что определенная функция является чистой и достаточно дорогой, и поэтому он компилирует ее для выполнения в другом потоке. Затем появляется оптимизатор платформы (например, JIT-компилятор HotSpot C2), который оптимизирует всю функцию. Теперь у вас есть пустой поток, ничего не делая. Или, представьте, опять же, javac решает запланировать функцию в другом потоке, и оптимизатор платформы может полностью оптимизировать его, за исключением того, что он не может выполнять встраивание через границы потоков, поэтому функция, которая может быть полностью оптимизирована, теперь выполняется без необходимости.
Таким образом, делать что-то подобное действительно имеет смысл, только если у вас есть один компилятор, выполняющий большинство оптимизаций за один раз, чтобы компилятор знал и мог использовать все различные оптимизации на разных уровнях и их взаимодействия друг с другом.
Следует отметить , что, например, компилятор HotSpot С2 JIT фактически делает выполнять некоторые автоматической векторизации, который также является одной из форм автоматического распараллеливания.
источник
definition
, используя разношерстнуюdefinition
изpurity
, вероятно , неясноеStringBuilder
), не имеет смысла, поэтому я бы отклонил это и просто предположил, что OP пишет javac, но означает Hotspot. Ваша проблема № 2 является довольно веской причиной против оптимизации чего-либо в javac.В этом ответе не было замечено ничего. Синхронное взаимодействие между потоками чрезвычайно дорого. Если функция может выполняться с частотой в несколько миллионов вызовов в секунду, на самом деле вам будет больно распараллеливать ее, а не оставлять ее как есть.
К сожалению, самая быстрая форма синхронной связи между потоками, использующая циклы занятости с атомарными переменными, к сожалению, неэффективна. Если вам приходится прибегать к использованию условных переменных для экономии энергии, производительность вашей связи между потоками страдает.
Таким образом, компилятор должен не только определить, является ли функция чистой, он также должен оценить время выполнения функции, чтобы увидеть, является ли распараллеливание чистым выигрышем. Кроме того, нужно будет выбирать между занятыми циклами, используя атомарные переменные или условные переменные. И это должно было бы создать темы за вашей спиной.
Если вы создаете потоки динамически, это даже медленнее, чем использование условных переменных. Таким образом, компилятору необходимо настроить определенное количество уже запущенных потоков.
Итак, ответ на ваш вопрос - нет , компиляторы недостаточно умны, чтобы автоматически распараллеливать чистые функции, особенно в мире Java. Они умны, не распараллеливая их автоматически!
источник