Чи може динамічна мова на зразок Ruby / Python досягати C / C ++ як продуктивність?


64

Цікаво, чи можна створити компілятори для динамічних мов, таких як Ruby, щоб мати схожу та порівнянну продуктивність з C / C ++? З того, що я розумію про компілятори, візьмемо, наприклад, Ruby, компіляція коду Ruby ніколи не може бути ефективною, оскільки спосіб обробки Ruby відображає такі функції, як автоматичне перетворення типу з цілого числа в велике ціле число, а також відсутність статичного набору тексту створює ефективний компілятор для Рубі надзвичайно важко.

Чи можливо побудувати компілятор, який може компілювати Ruby або будь-які інші динамічні мови в двійковий файл, який дуже тісно працює на C / C ++? Чи є фундаментальна причина, чому компілятори JIT, такі як PyPy / Rubinius, в кінцевому підсумку або ніколи не відповідають C / C ++ у продуктивності?

Примітка. Я розумію, що "продуктивність" може бути невизначеною, тому, щоб зрозуміти, що я мав на увазі, якщо ви можете робити X в C / C ++ з продуктивністю Y, чи можете ви зробити X в Ruby / Python з продуктивністю, близькою до Y? Де X - все - від драйверів пристроїв та коду ОС до веб-додатків.


1
Чи можете ви перефразувати питання, щоб воно заохочувало відповіді, підкріплені належними доказами щодо інших?
Рафаель

@Raphael Я пішов і редагував. Я думаю, що моя редакція принципово не змінює сенсу питання, але робить його менш привабливим.
Жиль

1
Зокрема, я думаю, що вам потрібно зафіксувати один (або кілька) конкретних заходів щодо ефективності. Виконання? Використання місця? Споживання енергії? Час розробника? Повернення інвестицій? Зауважте також це питання нашої мета, що стосується цього питання (точніше його відповідей).
Рафаель

Це питання є типовим ініціатором релігійних війн. І як ми бачимо з відповідей, ми маємо одну, хоч і дуже цивілізовану.
Андрій Бауер

Існують динамічні мови, які дозволяють додаткові анотації типу (наприклад: Clojure). З того, що мені відомо, продуктивність, пов’язана з функціями, що коментуються типом, еквівалентна тому, коли мова буде статично введена.
Педро Морте Роло

Відповіді:


68

Для всіх, хто сказав «так», я запропону відповідь, що відповідь - «ні», за задумом . Ці мови ніколи не зможуть відповідати продуктивності статично складених мов.

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

Однак є й інша сторона монети: цю додаткову інформацію потрібно відслідковувати. У сучасних архітектурах це вбивця продуктивності.

Вільям Едвардс пропонує хороший огляд аргументу .

Зокрема, оптимізації, згадані Косом, не можна застосовувати поза дуже обмеженою сферою, якщо ви не обмежите виразну силу ваших мов досить різко, про що говорив Девін. Це, звичайно, життєздатний компроміс, але заради обговорення ви згодом статечна мова, а не динамічна. Ці мови принципово відрізняються від Python або Ruby, як розуміє їх більшість людей.

Вільям наводить кілька цікавих слайдів IBM :

  • Кожна змінна може бути динамічно набрана: потрібні перевірки типу
  • Кожен оператор потенційно може викидати винятки через невідповідність типу тощо. Потрібні перевірки виключень
  • Кожне поле та символ можна додавати, видаляти та змінювати під час виконання: Потрібні перевірки доступу
  • Тип кожного об'єкта та його ієрархія класів можна змінювати під час виконання: Потрібні перевірки ієрархії класів

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

Крім того, Кос стверджує, що динамічні мови можуть навіть перевершити продуктивність на C ++. JIT дійсно може проаналізувати поведінку програми та застосувати відповідні оптимізації.

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

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

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


2
@Raphael Це тоді "недолік" компілятора. Зокрема, чи була javacколи-небудь профільна оптимізація? Не наскільки я знаю. Взагалі, не має сенсу робити компілятор мови JITted оптимізацією, оскільки JIT може це впоратися (і принаймні, таким чином більше мов отримує прибуток від зусиль). Тож (зрозуміло) javac, наскільки мені відомо, ніколи не було докладено великих зусиль для оптимізатора (для мов .NET це безумовно так).
Конрад Рудольф

1
@Raphael Ключове слово: можливо. Це ні в якому разі не показує. Це все, що я хотів сказати. Я мотивував свої припущення (але жодних доказів) для своїх припущень у попередніх пунктах.
Конрад Рудольф

1
@Бен я не заперечую, що це складно. Це просто інтуїція. Зрештою, відстеження всієї інформації під час виконання коштує дорого. Я не переконаний у вашій точці щодо IO. Якщо це передбачувано (= типовий випадок використання), то PGO може передбачити це. Якщо це неправдиво, я не переконаний, що і JIT може його оптимізувати. Можливо, раз у раз, із великої долі. Але надійно? …
Конрад Рудольф

2
@Konrad: Ваша інтуїція помилкова. Йдеться не про варіювання під час виконання, а про непередбачуваність під час компіляції . Найприємнішим місцем для JITs та статичної оптимізації є не те, коли поведінка програми змінюється під час виконання програми "занадто швидко" для профілювання, це коли поведінку програми легко оптимізувати в кожному окремому виконанні програми, але різко змінюється між пробіжки. Статичний оптимізатор, як правило, повинен оптимізувати лише один набір умов, тоді як JIT оптимізує кожен запуск окремо для умов, які трапляються в цьому циклі.
Бен

3
Примітка. Цей потік коментарів перетворюється на міні-чат. Якщо ви хочете продовжити це обговорення, будь ласка, принесіть його до чату. Коментарі слід використовувати для покращення початкової публікації. Будь ласка, припиніть цю розмову тут. Дякую.
Роберт Картенто

20

якщо ви можете робити X в C / C ++ з продуктивністю Y, чи можете ви зробити X в Ruby / Python з продуктивністю, близькою до Y?

Так. Візьмемо, як приклад, PyPy. Це колекція коду Python, який виконує близький до C, роблячи інтерпретацію (не все так близько, але не все так далеко). Вона робить це шляхом проведення аналізу повній програмі на вихідному коді , щоб присвоїти кожній змінної статичного типу (див Annotator і Rtyper документацію для деталей), а потім, коли збройні з тією ж інформацією типу ви даєте C, він може виконувати ті ж види оптимізації. Принаймні теоретично.

Звичайно, компроміс полягає в тому, що RPython приймає лише підмножину коду Python, і в цілому, навіть якщо це обмеження знято, тільки підмножина коду Python може зробити добре: підмножина, яку можна проаналізувати та надати статичні типи.

Якщо ви обмежите Python достатньо, можна створити оптимізатори, які можуть скористатися обмеженим підмножиною та скомпілювати його до ефективного коду. Це насправді не цікава цікавість, насправді це добре відомо. Але суть використання Python (або Ruby) в першу чергу полягала в тому, що ми хотіли використовувати цікаві функції, які, можливо, не добре аналізують і приводять до хорошої продуктивності! Тож цікаве питання насправді ...

Крім того, чи будуть компілятори JIT, такі як PyPy / Rubinius, коли-небудь відповідати C / C ++ у продуктивності?

Ні.

Під чим я маю на увазі: звичайно, можливо, у міру накопичення запуску коду ви можете отримати достатню кількість інформації про введення тексту та достатньо точок доступу, щоб зібрати весь код аж до машинного коду. І, можливо, ми можемо зробити це для кращого, ніж C, для деякого коду. Я не вважаю це надзвичайно суперечливим. Але це все одно має «прогріватися», а продуктивність все-таки трохи менш передбачувана, і вона не буде такою ж хорошою, як C або C ++ для певних завдань, які вимагають стабільно та передбачувано високої продуктивності.

Наявні дані про продуктивність Java, у яких є як більше інформації про типи, ніж Python чи Ruby, так і краще розвинений компілятор JIT, ніж Python або Ruby, як і раніше не відповідають C / C ++. Це, однак, у тому ж самому парку.


1
"Звичайно, компроміс полягає в тому, що приймається тільки підмножина коду Python, а точніше, лише підмножина коду Python може зробити добре: підмножина, яку можна проаналізувати та надати статичні типи." Це не зовсім точно. Тільки підмножина може взагалі приймати компілятор RPython. Компіляція з RPython на досить ефективний C працює лише тому, що складно складені частини Python гарантовано ніколи не зустрічаються в програмах RPython; це не просто те, що якщо вони відбудуться, вони не будуть оптимізовані. Це складений інтерпретатор, який обробляє весь Python.
Бен

1
Про JIT: Я бачив більше одного еталону, де Java виокремила кілька ароматів С (++). Здається, лише C ++ з підвищенням надійно залишається вперед. У такому випадку я цікавлюсь ефективністю за час розробника, але це вже інша тема.
Рафаель

@Ben: щойно у вас є RPython, тривіально створити компілятор / інтерпретатор, який повертається до використання інтерпретатора CPython, коли компілятор RPython виходить з ладу, тому "лише підмножина коду Python може зробити добре: ..." абсолютно точна.
Лежи Райан

9
@Raphael Не раз показано, що добре написаний код C ++ перевершує Java. Це "добре написана" частина, яка набагато складніше потрапити в C ++, тому в багатьох лавочках ви бачите результати, за якими Java перевершує C ++. C ++, отже, дорожче, але коли потрібен жорсткий контроль пам’яті та металева крупа, C / C ++ - це те, до чого ви звертаєтесь. Зокрема, це лише ас / р асемблер.
TC1

7
Порівняння максимальної продуктивності інших мов з такими мовами, як C / C ++, є своєрідним вправою безрезультатно, оскільки ви можете вбудовувати складання безпосередньо як частину мовної специфікації. Все, що машина могла б виконувати за допомогою програми, написаної будь-якою мовою, ви можете в гіршому випадку дублювати, написавши збірку з сліду виконаних інструкцій. Набагато цікавішою буде метрика, як підказує @Raphael у попередньому коментарі, продуктивність на кожне зусилля на розробку (людина-години, рядки коду тощо).
Patrick87

18

Коротка відповідь: ми не знаємо , запитай ще раз через 100 років. (Ми, можливо, ще не знаємо тоді; можливо, ми ніколи не дізнаємось.)

Теоретично це можливо. Візьміть всі програми, які коли-небудь були написані, переведіть їх вручну на найбільш ефективний машинний код і напишіть інтерпретатора, який відображає вихідні коди до машинних кодів. Це можливо, оскільки коли-небудь написано лише обмежену кількість програм (і як більше програм буде написано, продовжуйте ручні переклади). Це, звичайно, абсолютно ідіотично на практичних умовах.

Потім, теоретично, мови високого рівня можуть досягти продуктивності машинного коду, але вони не перевершать його. Це ще дуже теоретично, тому що в практичному плані ми дуже рідко вдаємося до написання машинного коду. Цей аргумент не застосовується для порівняння мов вищого рівня: це не означає, що C повинен бути більш ефективним, ніж Python, тільки що машинний код не може бути гіршим, ніж Python.

Походячи з іншого боку, на суто експериментальних умовах ми можемо побачити, що більшість часу інтерпретовані мови високого рівня діють гірше, ніж складені мови низького рівня. Ми схильні писати нечутливий до часу код на мовах дуже високого рівня та критичних за часом внутрішніх циклів у зборі, з такими мовами, як C та Python. Хоча я не маю жодної статистики для підтвердження цього, я думаю, що це дійсно найкраще рішення в більшості випадків.

Однак є беззаперечні випадки, коли мови високого рівня перемагають код, який можна було б реально написати: спеціальні середовища програмування. Такі програми, як Matlab і Mathematica, часто набагато краще вирішують певні види математичних задач, ніж те, що можуть писати прості смертні. Функції бібліотеки, можливо, були написані на C або C ++ (що є паливом до табору "мови низького рівня є ефективнішими"), але це не моя робота, якщо я пишу код Mathematica, бібліотека - це чорна скринька.

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

Мови високого рівня, як правило, роблять більше речей автоматичними, тому вони мають більше роботи, і, таким чином, є менш ефективними. З іншого боку, вони, як правило, мають більш семантичну інформацію, тому може бути простіше помітити оптимізацію (якщо ви пишете компілятор Haskell, вам не доведеться турбуватися, що інший потік змінить змінну під вашим носом). Одне з декількох зусиль для порівняння яблук і апельсинів різних мов програмування - це Computer Language Benchmark Game (раніше відомий як перестрілка). Фортран схильний світити при чисельних завданнях; але коли йдеться про маніпулювання структурованими даними або швидку комутацію потоків, F # і Scala добре справляються. Не сприймайте ці результати як євангелію: багато того, що вони вимірюють, - наскільки хорошим був автор тестової програми з кожної мови.

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

На серверних та настільних платформах процесори майже досягли плато, де вони не швидше (мобільні платформи також потрапляють туди); це сприяє мовам, де паралелізм легко експлуатувати. Багато процесорів проводять більшу частину свого часу в очікуванні відповіді на введення / виведення; час, витрачений на обчислення, має мало значення в порівнянні з кількістю вводу-виводу, а мова, яка дозволяє програмісту мінімізувати комунікації, є перевагою.

Загалом, хоча мови високого рівня починаються зі штрафу, вони мають більше можливостей для вдосконалення. Як близько вони можуть наблизитися? Запитай знову через 100 років.

Підсумкове зауваження: порівняння часто відбувається не між найефективнішою програмою, яка може бути написана мовою A, і тією ж мовою B, ні між найефективнішою програмою, коли-небудь написаною кожною мовою, а між найефективнішою програмою, яку можна написати людиною за певний час у кожній мові. Це вводить елемент, який неможливо проаналізувати математично, навіть в принципі. На практиці це часто означає, що найкраща продуктивність - це компроміс між тим, наскільки код низького рівня потрібно написати для досягнення цілей продуктивності, і тим, скільки коду низького рівня у вас є час написати, щоб зустріти дати випуску.


Я думаю, що відмінність між високим і низьким рівнем є неправильним. C ++ може бути (дуже) високим рівнем. І все ж сучасний C ++ на високому рівні (не обов'язково) не гірший, ніж еквівалент низького рівня - навпаки. C ++ та його бібліотеки були ретельно розроблені, щоб пропонувати абстракції високого рівня без штрафу за продуктивність. Те саме стосується і вашого прикладу Haskell: його абстракції високого рівня часто дозволяють, а не запобігають оптимізації. Первісне розмежування динамічних мов та статичних мов має більше сенсу в цьому плані.
Конрад Рудольф

@KonradRudolph Ви маєте рацію, що низький / високий рівень є дещо довільним відмінністю. Але динамічні та статичні мови також не охоплюють все; JIT може усунути велику різницю. По суті, відомі теоретичні відповіді на це питання тривіальні і марні, а практична відповідь - це "залежить".
Жиль

Отже, я думаю, що питання просто стає "наскільки хорошими можуть стати JIT, і якщо вони перегнають статичну компіляцію, чи можуть статично складені мови також отримувати від них користь?" Принаймні так я розумію питання, коли ми враховуємо JIT. І так, я згоден з вашою оцінкою, але, безумовно, ми можемо отримати кілька поінформованих здогадок, які виходять за рамки "це залежить". ;-)
Конрад Рудольф

@KonradRudolph Якщо б я хотів здогадуватися, я б запитав про програмування інженерії .
Жиль

1
На жаль, мовна перестрілка є сумнівним джерелом для кількісних орієнтирів: вони не приймають усі програми лише тих, що вважаються типовими для цієї мови. Це хитра і дуже суб’єктивна вимога; це означає, що ви не можете припустити, що реалізація перестрілки - це насправді користь (а на практиці деякі реалізації відверто відверто альтернативи). З фліп-боку; це мікро-орієнтири, а деякі реалізації використовують незвичні методи, які ви ніколи не враховували б у більш звичайному сценарії, щоб виграти продуктивність. Отже: це гра, не дуже надійне джерело даних.
Еймон Нербонна

10

Основна відмінність між висловлюванням C ++ x = a + bі затвердженням Python x = a + bє те , що компілятор C / C ++ можна сказати , з цього твердження (і трохи додаткової інформації , яку він має легкодоступний про типах x, aі b) саме те , що машинний код має бути виконано . У той час як для того, щоб розповісти про те, які операції буде робити оператор Python, вам потрібно вирішити проблему зупинки.

У C це твердження в основному компілюється до одного з декількох типів машинного додавання (і компілятор C знає, який саме). У C ++ він може компілюватися таким чином, або він може компілюватись до виклику статично відомої функції, або (в гіршому випадку), можливо, доведеться компілювати до віртуального пошуку і виклику методу, але навіть у цього є досить невеликий машинний код. Що ще важливіше, компілятор C ++ може визначити зі статистично відомих типів, що займаються, чи може він здійснити одну операцію швидкого додавання чи потрібно використовувати один з більш повільних варіантів.

В Python, компілятор теоретично може зробити майже що добре , якби він знав , що aі bобидва intс. Існують деякі додаткові накладні бокси, але якщо типи були відомі статистично, ви, ймовірно, могли позбутися і цього (хоча все ще представляючи інтерфейс, що цілі числа є об'єктами з методами, ієрархією суперкласів тощо). Проблема в тому, що компілятор для Python не можезнайте це, тому що класи, визначені під час виконання, можуть бути змінені під час виконання, і навіть модулі, що виконують визначення та імпорт, вирішуються під час виконання (і навіть які імпортні оператори виконуються залежать від речей, які можуть бути відомі лише під час виконання). Отже, компілятору Python слід було б знати, який код виконано (тобто вирішити проблему зупинки), щоб знати, що буде робити оператор, який він компілює.

Тож навіть із найскладнішими теоретично можливими аналізами , ви просто не можете сказати багато про те, що дана заява Python буде робити достроково. Це означає, що навіть якби був реалізований складний компілятор Python, майже у всіх випадках все одно доведеться випромінювати машинний код, який слід за протоколом пошуку словника Python для визначення класу об'єкта та пошуку методів (обхід MRO ієрархії класів, які також можуть динамічно змінюватися під час виконання, і тому їх складно скласти до простої таблиці віртуальних методів) і в основному робити те, що роблять (повільні) інтерпретатори. Ось чому насправді немає складних оптимізуючих компіляторів для динамічних мов. Створити його не просто важко, максимально можлива виплата

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

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

Існують різні компілятори JIT для динамічних мов, які стверджують, що досягають швидкості виконання, порівнянної зі швидкістю компільованого та оптимізованого C / C ++. Існують навіть оптимізації, які можуть бути виконані компілятором JIT, який неможливо зробити достроково компілятором для будь-якої мови, тому теоретично компіляція JIT (для деяких програм) може одного дня перевершити найкращий статичний компілятор. Але, як справедливо зазначав Девін, властивості компіляції JIT (лише "гарячі точки" швидкі, і лише після періоду розминки) означають, що динамічні мови, складені JIT, навряд чи коли-небудь підходять для всіх можливих додатків, навіть якщо вони стануть як швидше або швидше, ніж статично складені мови загалом.


1
Ось тепер два голоси, що не мають коментарів. Я вітаю пропозиції щодо вдосконалення цієї відповіді!
Бен

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

@mikera Вибачте, але ні, ви невірні. Ніхто ще не реалізував компілятор (у тому розумінні, що ми розуміємо, що GCC є компілятором) для повністю загального Python або інших динамічних мов. Кожна така система працює лише для підмножини мови або лише для певних програм, або іноді висилає код, який в основному є інтерпретатором, що містить жорстко кодовану програму. Якщо ви хочете, я напишу вам програму Python, що містить рядок, foo = x + yде передбачення поведінки оператора додавання на час компіляції залежить від вирішення проблеми зупинки.
Бен

Я маю рацію і думаю, що ви пропускаєте суть. Я сказав "за багатьох обставин". Я не сказав "за всіх можливих обставин". Те, чи ви можете побудувати надуманий приклад, пов’язаний із проблемою зупинки, в реальному світі значною мірою не має значення. FWIW, ви також можете побудувати подібний приклад для C ++, щоб ви нічого не доказували. У всякому разі, я не прийшов сюди, щоб вступати в суперечки, аби лише запропонувати покращення вашої відповіді. Прийняти його або залишити його.
mikera

@mikea Я думаю, що ви, можливо, пропустите точку. Для того, щоб компілювати x + yдо ефективних операцій додавання машини, вам потрібно знати під час компіляції, чи це ні. Весь час , а не лише деякий час. Для динамічних мов це практично неможливо з реалістичними програмами, навіть якщо розумна евристика здогадається більшу частину часу. На компіляцію потрібні гарантії часу компіляції . Отже, говорячи про "за багатьох обставин", ви насправді взагалі не звертаєтесь до моєї відповіді.
Бен

9

Просто швидкий вказівник, який окреслює найгірший сценарій для динамічних мов:

Розбір Perl не обчислюється

Як наслідок, (повний) Perl ніколи не можна скласти статично.


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

Ще один момент, про який слід пам’ятати, це те, що динамічні мови вирішують іншу проблему, ніж C. C ледве більше ніж приємний синтаксис для асемблера, тоді як динамічні мови пропонують багаті абстракції. Продуктивність виконання часто не є основною проблемою: наприклад, час виходу на ринок залежить від того, чи зможуть розробники писати складні, якісні системи в короткі терміни. Розширення без рекомпіляції, наприклад, із плагінами, - ще одна популярна особливість. Якій мові ви віддаєте перевагу в цих випадках?


5

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

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

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

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

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

На практиці співставлення продуктивності динамічної мови зі статичною мовою може зажадати повторної реалізації інтерпретатора / часу виконання в статичній мові; однак, що ви взагалі можете це зробити - суть і суть цього аргументу. Це питання щодо курки та яєць, і, якщо ви погоджуєтесь із недоведеними (хоча, на мою думку, переважно зрозумілими) припущеннями, висловленими вище, ми можемо реально відповісти на це; ми повинні дати кивати статичним, а не динамічним мовам.

Інший спосіб відповісти на це питання, з огляду на цю дискусію, такий: у збереженій програмі управління = модель даних обчислень, яка лежить в основі сучасних обчислень, відмінність між статичною та динамічною компіляцією є помилковою дихотомією; Статично складені мови повинні мати засоби для генерації та виконання довільного коду під час виконання. Це принципово пов'язане з універсальним обчисленням.


Перечитавши це, я не думаю, що це правда. Компіляція JIT порушує ваш аргумент. Навіть найпростіший код, наприклад, main(args) { for ( i=0; i<1000000; i++ ) { if ( args[0] == "1" ) {...} else {...} }може значно прискорити, коли значення argsстане відомо (якщо припустити, що воно ніколи не змінюється, що ми можемо стверджувати). Статичний компілятор не може створити код, який ніколи не скасовує порівняння. (Звичайно, у цьому прикладі ви просто витягнете ifз петлі. Але справа може бути і більш перекрученою.)
Рафаель

@Raphael Я думаю, що JIT, ймовірно, робить мій аргумент. Програми, що виконують компіляцію JIT (наприклад, JVM), як правило, є статично складеними програмами. Якщо статично складена програма JIT могла запустити скрипт швидше, ніж яка-небудь інша статична програма могла виконати ту саму роботу, просто «зв’яжіть» скрипт із компілятором JIT і викликайте пакет статично складеною програмою. Це повинно робити як мінімум так само, як JIT, що працює за окремою динамічною програмою, що суперечить будь-якому аргументу, що JIT повинен робити краще.
Patrick87

Гм, це як би сказати, що поєднання сценарію Ruby з його інтерпретатором дає вам статично складену програму. Я не згоден (оскільки він усуває всі розрізнення мов у цьому плані), але це питання семантики, а не понять. Концептуально, адаптуючи програму під час виконання (JIT), можна зробити оптимізацію, яку ви не можете зробити під час компіляції (статичний компілятор), і це моя думка.
Рафаель

@Raphael Немає значущого розрізнення - це щось на кшталт відповіді: будь-яка спроба жорстко класифікувати деякі мови як статичну, і, таким чином, страждаючи від обмежень продуктивності, провалюється саме з цієї причини: немає переконливої ​​різниці між попередньо упакованим (Ruby , скрипт) пакет та програма C. Якщо (Ruby, скрипт) може змусити машину виконати правильну послідовність інструкцій, щоб ефективно вирішити задану проблему, то це могла б вміло складена програма C.
Patrick87

Але ви можете визначити різницю. Один варіант надсилає код під рукою процесору без змін (C), інший компілюється під час виконання (Ruby, Java, ...). Перше - це те, що ми маємо на увазі під "статичною компіляцією", тоді як останньою буде "саме в часі компіляція" (що дозволяє оптимізацію залежних від даних).
Рафаель

4

Чи можете ви створити компілятори для динамічних мов, таких як Ruby, щоб мати схожі та порівнянні показники з C / C ++?

Я думаю, що відповідь "так" . Я також вважаю, що вони можуть навіть перевершити поточну архітектуру C / C ++ з точки зору ефективності (навіть якщо незначно).

Причина проста: у процесі виконання більше інформації, ніж у час компіляції.

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

Дивіться Динамічні мови відбиття назад , промову Стіва Йегге з Google (також там я вірю відео). Він згадує деякі конкретні методи оптимізації JIT від V8. Натхненно!

Я з нетерпінням чекаю, що ми будемо мати в наступні 5 років!


2
Я люблю оптимізм.
Дейв Кларк

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

1
@DaveClarke ось що змушує мене бігати :)
Кос

2

Люди, які, мабуть, думають, що це теоретично можливо чи в далекому майбутньому, на мою думку, абсолютно помиляються. Сенс полягає в тому, що динамічні мови надають і накладають зовсім інший стиль програмування. Насправді різниця є двоякою, навіть якщо обидва аспекти взаємопов'язані:

  • Символи (найрізноманітніші id <-> прив’язки дат усіх видів) не вводяться.
  • Структури (дані, все, що живе під час виконання) також нетипові, а також за типом їх елементів.

Другий момент забезпечує загальність безкоштовно. Зауважте, що структури тут - це складові елементи, колекції, але й самі типи, і навіть (!) Підпрограми всіх видів (функції, дії, операції) ... Ми могли б вводити структури за їх типами елементів, але завдяки першому моменту перевірка відбудеться в будь-який час. Ми могли б вводити символи та ще структурувати ті, які не типові відповідно до їх типів елементів (масив aпросто вводиться як масив, а не як масив ints), але навіть ця декілька не відповідає дійсності динамічної мови ( aможе також містити рядок).

L

  • ElementLL
  • ElementL
  • всі структури (знову ж таки, модельні процедури) отримують лише елементи елемента

Для мене зрозуміло, що це лише величезна перф-кара; і я навіть не торкаюся всіх наслідків (безліч перевірок виконання всіх видів, необхідних для забезпечення чутливості програми), добре описаних в інших публікаціях.


+1 Дуже цікаво. Ви читали мою відповідь? Ваше та моє мислення здаються схожими, хоча ваша відповідь дає більше деталей та цікаву перспективу.
Patrick87

Динамічні мови не повинні бути типізованими. Реалізація моделі динамічної мови на С сильно обмежує можливості оптимізації; компілятору дуже важко розпізнавати оптимізацію високого рівня (наприклад, незмінні дані) та змушує деякі основні операції (наприклад, виклики функцій) пройти через C. Те, що ви описуєте, не далеко від інтерпретатора байт-коду з розшифровкою байт-коду. попередньо оцінено; нативні компілятори, як правило, працюють значно краще, я сумніваюся, що розшифровка байт-коду може виправдати різницю.
Жиль

@ Patrick87: ти маєш рацію, наші думки здаються дуже схожими (раніше не читав, вибачте, моє рефлексія походить від того, що зараз реалізується dyn lang в C).
спр

@Gilles: Я скоріше погоджуюся з приводу "... не треба вводити текст", якщо ви маєте на увазі не статичний текст . Але, мабуть, це не те, що люди думають про кольорові мотиви взагалі. Я особисто вважаю щедрість (у загальному сенсі, наведену у відповіді вище) особливістю, набагато сильнішою і без якої важче жити. Ми можемо легко знайти способи поводження зі статичними типами, розширивши своє мислення про те, як може бути визначений даний (здавалося б, поліморфний) тип, або надавши більше гнучкості для екземплярів безпосередньо.
спр

1

У мене не було часу детально прочитати всі відповіді ... але мене розвеселило.

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

Існувала навіть концепція розширення коду: відношення розміру коду, що виробляється компілятором, до розміру коду для тієї ж програми, що виробляється хорошим програмістом (наче їх було занадто багато :-). Звичайно, ідея полягала в тому, що це співвідношення завжди було більше 1. Мовами того часу були Кобол і Фортран 4, або Алгол 60 для інтелектуалів. Я вважаю, що Лісп не вважався.

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

Дивні риси, динамічні особливості мови не мають значення. Важливо, як вони використовуються, чи використовуються вони. Продуктивність у будь-якій одиниці (розмір коду, швидкість, енергія, ...) часто залежить від дуже малих частин програм. Отже, є хороший шанс, що засоби, які надають виразну силу, насправді не завадять. З хорошою практикою програмування, вдосконалені засоби використовуються лише дисципліновано, щоб уявити собі нові структури (це був урок лісу).

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

В ході обговорення було декілька посилань на аналіз найгіршого випадку ("проблема зупинки", розбір PERL). Але в гіршому випадку аналіз здебільшого не має значення. Важливо те, що відбувається в більшості випадків або в корисних випадках ... як би це не було визначено, зрозуміло чи пережито. Ось ще одна історія, безпосередньо пов’язана з оптимізацією програми. Це відбулося дуже давно у великому університеті Техасу, між докторантом і його радником (який згодом був обраний в одну з національних академій). Як я пам'ятаю, студент наполягав на вивченні проблеми аналізу / оптимізації, яку радник виявив непридатною. Незабаром вони вже не розмовляли. Але студент мав рацію: проблема була досить простежуваною в більшості практичних випадків, тому дисертація, яку він написав, стала довідковою роботою.

І далі коментувати твердження, що Perl parsing is not computableвсе, що мається на увазі під цим реченням, існує аналогічна проблема з ML, яка є надзвичайно добре формалізованою мовою. Type checking complexity in ML is a double exponential in the lenght of the program.Це дуже точний і формальний результат в гіршому випадку складності ... що зовсім не має значення. Користувачі Afaik, ML все ще чекають на практичну програму, яка підірве перевірку типу.

У багатьох випадках, як і раніше, людський час та компетентність обмежуються, ніж обчислювальні потужності.

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

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

Але у випадку програмування нам, можливо, доведеться переписати всі докази з нуля (програми - це докази). Можливо, те, що нам насправді потрібно, - це дуже високий рівень та розвинені мови програмування. Дизайнери оптимізаторів із задоволенням слідкують.


0

Пара приміток:

  • Не всі мови високого рівня є динамічними. У Haskell дуже високий рівень, але він повністю статично набраний. Навіть такі мови програмування, як Rust, Nim та D, можуть висловлювати абстракції високого рівня коротко та ефективно. Насправді вони можуть бути такими ж стислими, як і динамічні мови.

  • Існують сильно оптимізовані дострокові компілятори для динамічних мов. Хороші реалізації Lisp досягають половини швидкості еквівалентної C.

  • Компіляція JIT може стати великою виграшею тут. Брандмауер веб-додатків CloudFlare генерує код Lua, який виконується LuaJIT. LuaJIT суттєво оптимізує реально пророблені контури (як правило, не атакуючі контури), в результаті чого код працює набагато швидше, ніж код, створений статичним компілятором на фактичному навантаженні. На відміну від статичного компілятора з керованою профілем оптимізацією, LuaJIT адаптується до змін у шляхах виконання під час виконання.

  • Деоптимізація також має вирішальне значення. Замість коду, зібраного JIT, потрібно перевірити наявність класу маніпуляцій, акт маніпуляції маніпуляцій викликає гак у системі виконання, який відкидає машинний код, який залежав від старого визначення.


Як це відповідь? Ну, куля три, може, якщо ви додали посилання.
Рафаель

Я дуже скептично ставляться до твердження, що PGO не міг відповідати продуктивності коду веб-додатків LuaJIT за типового навантаження.
Конрад Рудольф

@KonradRudolph Ключовою перевагою JIT є те, що JIT адаптує код, оскільки різні шляхи нагріваються.
Демі

@Demetri Я це знаю. Але дуже важко оцінити, чи є це перевагою - дивіться мою відповідь та обговорення коментарів там. Коротше кажучи: хоча JIT може адаптуватися до змін у використанні, він також повинен відслідковувати матеріали під час виконання, що спричиняє накладні витрати. Беззбитковість цього інтуїтивно зрозуміла лише там, де відбуваються часті зміни поведінки. Для веб-додатків, ймовірно, є лише одна (або дуже мало) схема використання, оптимізація якої окупається, тому мінімальне підвищення продуктивності завдяки адаптованості не компенсує накладні витрати на постійне профілювання.
Конрад Рудольф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.