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