Я погоджуюся з Дітріхом Еппом: це поєднання кількох речей, які роблять GHC швидким.
Перш за все, Хаскелл дуже високого рівня. Це дозволяє компілятору проводити агресивні оптимізації, не порушуючи код.
Подумайте про SQL. Тепер, коли я пишу SELECT
заяву, це може виглядати як імперативний цикл, але це не так . Це може виглядати так, що він перетинає всі рядки в цій таблиці, намагаючись знайти той, що відповідає заданим умовам, але насправді "компілятор" (двигун БД) міг би робити пошук індексу - який має зовсім інші характеристики продуктивності. Але оскільки SQL настільки високого рівня, "компілятор" може підміняти абсолютно різні алгоритми, прозоро застосовувати декілька процесорів або каналів вводу / виводу або цілі сервери тощо.
Я думаю, що Хаскелл такий самий. Ви можете подумати, що ви просто попросили Хаскелла зіставити список введення у другий список, відфільтрувати другий список до третього списку, а потім порахувати, скільки результатів вийшло. Але ви не бачили, як GHC застосовує правила перезапису потокового синтезу за кадром, перетворюючи всю річ у єдиний жорсткий цикл машинного коду, який виконує всю роботу за один прохід над даними без розподілу - таку річ, яка була б бути втомливим, схильним до помилок і не піддається допису вручну. Це дійсно можливо лише через відсутність детально низьких даних у коді.
Ще один спосіб поглянути на це може бути ... чому не повинен Haskell бути швидким? Що це робить, щоб зробити це повільним?
Це не інтерпретована мова, як Perl чи JavaScript. Це навіть не така система віртуальної машини, як Java або C #. Він компілюється аж до рідного машинного коду, тому ніяких накладних витрат там немає.
На відміну від мов OO [Java, C #, JavaScript…], Haskell має стирання повного типу [як C, C ++, Pascal ...]. Перевірка всіх типів відбувається лише під час компіляції. Таким чином, не існує перевірки типу виконання часу, щоб також сповільнити вас. (Немає жодної перевірки нульових покажчиків. У цьому випадку, скажімо, Java, JVM повинен перевірити наявність нульових покажчиків і викинути виняток, якщо вам це належить. Haskell не повинен турбуватися з цією перевіркою.)
Ви говорите, що це звучить повільно, "створюючи функції під час руху під час виконання", але якщо ви подивитеся дуже уважно, ви насправді цього не робите. Це може виглядати так, як ти, але цього не робиш. Якщо ви скажете (+5)
, ну, це важко закодовано у ваш вихідний код. Він не може змінитися під час виконання. Тож насправді це не динамічна функція. Навіть криві функції - це просто збереження параметрів у блок даних. Весь виконуваний код фактично існує під час компіляції; немає інтерпретації часу виконання. (На відміну від деяких інших мов, які мають "функцію eval".)
Подумайте про Паскаля. Він старий, і його ніхто вже не використовує, але ніхто не скаржиться, що Паскаль повільний . Є багато чого, що йому не сподобається, але повільність насправді не одна з них. Haskell насправді не робить так багато, що відрізняється від Pascal, за винятком того, що збирає сміття, а не ручне управління пам’яттю. І незмінні дані дозволяють здійснити декілька оптимізацій для двигуна GC [яке лінива оцінка потім дещо ускладнює].
Я думаю, річ у тому, що Хаскелл виглядає передовим, витонченим та високим рівнем, і всі думають: "О, ух, це справді потужно, це повинно бути дивно повільно! " Але це не так. Або принаймні, це не так, як ви очікували. Так, у нього дивовижна система типу. Але ти знаєш що? Це все відбувається під час компіляції. До часу виконання його вже немає. Так, це дозволяє будувати складні ADT з рядком коду. Але ти знаєш що? ADT - це просто звичайний C union
of struct
s. Більше нічого.
Справжній вбивця - лінива оцінка. Коли ви правильно зрозумієте суворість / лінь свого коду, ви можете написати тупо швидкий код, який все ще є елегантним і красивим. Але якщо ви помиляєтесь з цим матеріалом, ваша програма йде в тисячі разів повільніше , і це дійсно не очевидно, чому це відбувається.
Наприклад, я написав тривіальну маленьку програму, щоб підрахувати, скільки разів кожен байт з'являється у файлі. Для вхідного файлу розміром 25 Кб програмі знадобилося 20 хвилин, і проковтнув 6 гігабайт оперативної пам’яті! Це абсурд !! Але потім я зрозумів, в чому проблема, додав єдиний вибух, і час запуску знизився до 0,02 секунди .
Тут Хаскелл несподівано йде повільно. І це звичайно потребує певного часу, щоб звикнути. Але з часом стає простіше писати дійсно швидкий код.
Що робить Haskell настільки швидким? Чистота. Статичні типи. Лінь. Але перш за все, будучи достатньо високим рівнем, що компілятор може кардинально змінити реалізацію, не порушуючи очікувань вашого коду.
Але я думаю, це лише моя думка ...