Чи компілятори на зразок Javac автоматично визначають чисті функції та паралельно їх?


12

Як відомо, чисті функції полегшують парелелізацію. Що це за функціональне програмування, що робить його притаманним при паралельному виконанні?

Чи достатньо розумні компілятори, такі як Javac, щоб виявити, коли метод є чистою функцією? Завжди можна реалізувати класи, які реалізують функціональні інтерфейси, такі як Function , але мають побічні ефекти.


7
Питання полягає не лише в тому, чи може компілятор знати, чи функція чиста, але й чи може вона інтелектуально планувати паралельне виконання чистих функцій. Недостатньо, щоб розпалити нову нитку для кожного: це неефективно. GHC (Haskell) займається цим, використовуючи лінь та "зелені нитки"; Я був би чесно здивований, якби будь-яка нечиста мова навіть спробувала, враховуючи додаткові труднощі, щоб переконатися, що чисті нитки були правильно заплановані щодо основної нечистої нитки.
Райан Райх

@RyanReich, чи є якісь покращення продуктивності використання функціонального програмування на чистій функціональній мові, такі як Java? чи є приріст функціонального програмування чисто функціональним, таким як модульність?
Naveen

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

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

6
javac - компілятор Java, який приймає вихідний код java та генерує файли класів байтового коду Java. Він досить обмежений, що він може (і повинен) зробити. Він не має свободи чи необхідних базових механізмів для введення паралелізму у файл класу коду байтів.
Ерік Ейдт

Відповіді:


33

такі компілятори, як Javac, досить розумні, щоб виявити, коли метод є чистою функцією.

Це не питання "досить розумного". Це називається Аналіз чистоти і в загальному випадку неможливо: це рівнозначно вирішенню проблеми зупинки.

Тепер, звичайно, оптимізатори весь час роблять неможливо неможливі речі, "очевидно неможливо в загальному випадку" не означає, що він ніколи не працює, це означає лише, що він не може працювати у всіх випадках. Отже, є насправді алгоритми, щоб перевірити, чи функція чиста чи ні, просто частіше результат буде "я не знаю", це означає, що з міркувань безпеки та правильності потрібно припустити що ця конкретна функція може бути нечистою.

І навіть в тих випадках , коли він робить роботу, алгоритми є складними і дорогими.

Отже, це проблема №1: вона працює лише в особливих випадках .

Завдання №2: Бібліотеки . Для того, щоб функція була чистою, вона може коли-небудь викликати чисті функції (а ті функції можуть викликати лише чисті функції тощо) тощо. Javac, очевидно, знає лише Java, і він знає лише про код, який він може бачити. Отже, якщо ваша функція викликає функцію в іншому блоці компіляції, ви не можете знати, чи вона чиста чи ні. Якщо він викликає функцію, написану іншою мовою, ви не можете знати. Якщо він викликає функцію в бібліотеці, яка ще може бути навіть не встановлена, ви не можете знати. І так далі.

Це працює лише тоді, коли у вас є цілопрограмний аналіз, коли вся програма написана однією і тією ж мовою, і все складається відразу за один раз. Ви не можете використовувати жодну бібліотеку.

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

Як ви, напевно, здогадувались, зрозуміти, як довго триватимуть обчислення, в загальному випадку неможливо (ми навіть не можемо розібратися, чи це займе обмежений час, не кажучи вже про те, скільки часу) і важко і дорого навіть у особливий випадок.

Убік: Javac та оптимізації . Зауважте, що більшість реалізацій javac насправді не виконують багато оптимізацій. Наприклад, реалізація javac Java Oracle покладається на базовий механізм виконання програм для оптимізації . Це призводить до ще одного набору проблем: скажімо, javac вирішив, що певна функція є чистою і досить дорогою, і тому вона компілює її для виконання в іншій нитці. Потім іде оптимізатор платформи (наприклад, компілятор HotSpot C2 JIT) і оптимізує всю функцію. Тепер у вас порожня нитка нічого не робить. Або, уявіть, знову ж таки, javac вирішує запланувати функцію на інший потік, і оптимізатор платформи міг би повністю оптимізувати його, за винятком того, що він не може виконувати вбудовані через межі потоку, тому функція, яку можна було б повністю оптимізувати, тепер непотрібно виконувати.

Отже, робити щось подібне дійсно має сенс, якщо у вас є один компілятор, який здійснює більшість оптимізацій за один раз, щоб компілятор знав про і міг використовувати всі різні оптимізації на різних рівнях та їх взаємодію між собою.

Слід зазначити , що, наприклад, компілятор HotSpot С2 JIT фактично робить виконувати деякі автоматичної векторизації, який також є однією з форм автоматичного розпаралелювання.


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

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

1
Ваша проблема №2 здебільшого не відповідає тому, що практично всі оптимізації виконуються JIT (ви, очевидно, це знаєте, але ігноруєте). Аналогічно проблема №3 стає частково недійсною, оскільки JIT сильно покладається на статистику, зібрану перекладачем. Я особливо не погоджуюся з "Ви не можете використовувати жодну бібліотеку", оскільки деоптимізація допомагає. Я згоден, що додаткова складність була б проблемою.
maaartinus

2
@maaartinus: Окрім того, лише явак моєї відповіді стосується яваку. Я спеціально робити згадка, що , наприклад, «Це працює тільки, якщо у вас є аналіз цільної програми, коли вся програма написана на тій же мові, і все компілюється відразу за один раз.» Це, очевидно, справедливо для C2: він має справу лише з однією мовою (байт-код JVM), і він має доступ до всієї програми відразу.
Йорг W Міттаг

1
@ JörgWMittag Я знаю, що ОП запитує про javac, але я б обміняв, що вони припускають, що саме javac відповідає за оптимізацію. І що вони навряд чи знають, що є С2. Я не кажу, ваша відповідь погана. Просто дозволяти javac робити будь-яку оптимізацію (за винятком дрібниць, таких як використання StringBuilder) - це не має сенсу, тому я відхиляю це і просто припускаю, що ОП пише javac, але означає Hotspot. Ваша проблема №2 є досить вагомою причиною проти оптимізації чого-небудь у javac.
maaartinus

5

Отримана відповідь не відзначила жодного. Синхронний зв’язок між потоками надзвичайно дорогий. Якщо функція здатна виконуватись зі швидкістю багато мільйонів викликів в секунду, вона насправді зашкодить вам більше паралелізувати її, а не залишити її такою, якою є.

Найшвидша форма синхронного міжпотокового зв'язку з використанням зайнятих циклів з атомними змінними, на жаль, енергоефективна. Якщо вам доведеться вдатися до використання змінних умов для економії енергії, продуктивність вашого міжпотокового зв'язку страждає.

Отже, компілятору потрібно не лише визначати, чи функція чиста, вона також повинна оцінити час виконання функції, щоб побачити, чи є паралелізація чистим виграшем. Крім того, потрібно буде вибирати між зайнятими циклами, використовуючи атомні змінні або змінні умови. І потрібно було б створити нитки за спиною.

Якщо ви створюєте потоки динамічно, це навіть повільніше, ніж використання змінних умов. Отже, компілятору потрібно було б встановити певну кількість потоків, які вже запущені.

Отже, відповідь на ваше запитання - ні , компілятори недостатньо "розумні", щоб автоматично паралелізувати чисті функції, особливо в світі Java. Вони розумні, не автопаралелізуючи їх!


5
" Вони розумні, не автопаралелізуючи їх! " : Це надто далеко. Хоча це правда, що паралелізація у будь-якій можливій точці лише заради неї, як правило, виявляється неефективною, розумний компілятор визначить практичну стратегію паралелізації. Я думаю, що більшість людей це розуміє, тому коли ми говоримо про автоматичну паралелізацію, ми маємо на увазі авто-практичну-паралелізацію.
Нат

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

Я згоден - коментар @ Nat передбачає, що паралелізація не обов'язково означає декілька потоків, що правда. Наприклад, JIT може вбудувати декілька викликів до чистої функції та переплутати їх інструкції щодо процесора в певних випадках. Наприклад, якщо обидва методи викликають константу, вона може бути отримана один раз і збережена в регістрі процесора для обох екземплярів використовуваного методу. Сучасні процесори - це звіри з численними регістрами загального призначення та спеціалізованими інструкціями, які можуть бути дуже корисними при оптимізації коду.

1
@Joshua: Насправді набагато простіше для компілятора JIT. Компілятор JIT також може зрозуміти, що функція може бути не чистою, але поки що жоден виклик не викликав нечисту поведінку.
gnasher729

Я згоден з @Joshua. У мене важко паралельний алгоритм на роботі. Я намагався вручну паралелізувати це, навіть роблячи кілька спрощених наближень (і, таким чином, змінюючи алгоритм), і щоразу жаліло провалювався. Навіть програма, яка розповідає, чи можна паралелізувати щось надзвичайно важко, хоча це було б набагато простіше, ніж насправді паралелізувати це. Пам'ятайте, що ми говоримо про цілісні мови програмування.
juhist
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.