Готовый перевод A Radical Approach to Application Development / Радикальный подход к разработке приложений: 2.1 Myth 1: Lisp Needs a Compiler
На самом деле, это самый главный миф. Если вы участвовали в групповых обсуждениях Lisp, то знаете, что компилятор играет главную роль. Может сложиться впечатление, что это почти синоним среды исполнения. Людям важно, какие действия компилятор производит с кодом и насколько эффективно. Если вам кажется, что программа на Lisp медленно выполняется, то вы решите, что вам требуется компилятор получше.
Идея интерпретируемого Lisp расценивается как старое заблуждение. Для современного Lisp требуется компилятор, а интерпретатор - это лишь полезное дополнение, и нужен для интерактивной отладки приложения. Он слишком медленный и раздутый для выполнения программ, которые уже находятся на этапе готовности к выпуску.
Мы уверены, что противоположная точка зрения верна. С одной стороны (и не только с философской точки зрения) скомпилированная программа на Lisp больше не является Lisp вообще. Это нарушает фундаментальное правило "формальной эквивалентности кода и данных". Получившийся код не содержит S-выражений и не может быть обработан Lisp. Исходный язык (Lisp) преобразовывется в другой язык (машинный код) с неизбежными несовместимостями на разных машинах.
На практике, компилятор усложняет систему в целом. Такие особенности, как стратегии множественного связывания, типизированные переменные и макросы были разработаны для удовлетворения нужд компиляторов. Система получается раздутой, потому что она должна поддерживать также и интерпретатор, и соответственно, две достаточно различные архитектуры,
Но стоит ли это затраченных усилий? Конечно, скорость выполнения выше, и создание компилятора интересно в целях обучения. Но мы утверждаем, что в повседневной жизни хорошо спроектированный "интерпретатор" часто может превзойти скомпилированную систему.
Вы понимаете, что на самом деле мы не говорим об "интерпретации". Lisp-система сразу конвертирует передаваемые ей данные во внутренние структуры указателей, которые называются "S-выражениями". Истинная "интерпретация" работает с одномерными кодами символов, и это существенно замедляет процесс выполнения. Lisp же "вычисляет" S-выражения быстро следуя по этим структурам указателей. Нет никаких поисков, так что ничего на самом деле не "интерпретируется". Но мы будем придерживаться этого привычного термина.
Программа на Lisp как S-выражение образует дерево исполняемых узлов. Код этих узлов написан обычно на оптимизированном С или ассемблере, поэтому задача интерпретатора состоит в том, чтобы передать управление от одного узла другому. Поскольку многие из этих встроенных функций Lisp очень мощные и выполняют много вычислений, большая часть времени выполнения приходится на узлы. Само дерево функционирует как своего рода клей.
Компилятор Lisp убирает немного этого клея и заменяет некоторые узлы с примитивной или потоковой функциональностью напрямую в машинный код. Но, поскольку в любом случае большая часть времени выполнения приходится на встроенные функции, эти улучшения не столь драматичны, как например у компилятора байт-кода Java, для которого каждый узел (байт-код) обладает сравнительно примитивной функциональностью.
Конечно, компиляция сама по себе также требует довольно много времени. Сервер приложений часто исполняет исходные файлы Lisp на лету за один прогон и немедленно отбрасывает код, как только он выполнился. В таких случаях, либо изначально более медленный интерпретатор Lisp-системы, основанной на компиляторе, либо дополнительное время, затрачиваемое компилятором, будут значительно снижать общую производительность.
Внутренние структуры Pico Lisp были изначально разработаны для удобства интерпретации. Несмотря на то, что они были полностью написаны на C, и не были специально оптимизированы для скорости выполнения, никогда не было проблемы недостаточной производительности. Первая коммерческая система, написанная на Pico Lisp, представляла собой систему для обработки и ретуширования изображений, а также для создания макетов страниц для печати. Она была создана в 1988 году и использовалась на Mac II с ЦПУ 12 МГц и оперативной памятью 8 МБ. Конечно, не было компилятора Lisp, были только низкоуровневые манипуляции с пикселями и функции безье, написанные на C. Даже тогда, при работе на компьютере, в сотни раз медленнее современных, никто не жаловался на производительность.
Чисто ради интереса я установил CLisp и сравнил его с Pico Lisp ан примере простых тестов. Конечно, это не означает, что результаты тестов показывают полезность той или иной системы в качестве сервера приложений, но они дают приблизительное представление о производительности этих систем. Сначала, я попытался выполнить простую рекурсивную функцию Фибоначчи.
(defun fibo (N)
(if (< N 2)
1 (+ (fibo (- N 1)) (fibo (- N 2)) ) ) )
При вызове этой функции с параметром 30 (fibo 30), я получил следующие результаты (тестирование выполнялось на ноутбуке Pentium-I 266 МГц):
Pico (интерпретация) 12 секунд
CLisp интерпретация 37 секунд
CLisp компилированный за 7 секунд.
Интерпретатор CLisp почти в три раза медленнее, а компилятор чуть ли не в два раза быстрее Pico Lisp.
Однако функция Фибоначчи не очень хороший пример типичной Lisp-программы. Она состоит только из примитивного потока и арифметических функций, что легко оптимизируется компилятором и может быть написано прямо на C, если это критично по времени (в этом случае выполнение заняло бы всего 0.2 с)
Поэтому я взял другой крайний случай, с функцией, выполняющей обширную обработку списков:
(defun tst () (mapcar
(lambda (X) (cons
(car X) (reverse (delete (car X) (cdr X))) ) ) '((a b c a b c) (b c d b c d) (c d e c d e) (d e f d e f)) ) )
Вызвав эту функцию 1 млн раз, я получил:
Pico (интерпретация) 31 sec
CLisp интерпретация 196 sec
CLisp компилированный 80 sec
Теперь интерпретатор CLisp более чем в 6 раз медленнее, но к моему удивлению даже скомпилированный код в 2.58 раз медленнее чем Pico Lisp.
Может быть у CLisp медленный компилятор? И возможно код может быть ускорен с помощью некоторых трюков. Но эти результаты все равно оставляют много сомнений в том, могут ли быть оправданы накладные расходы на компиляцию. Возиться с оптимизацией компиляторов это последнее, что я хочу делать, когда дело касается логики приложения и когда пользователь все равно не заметит задержек.
Переведено на Нотабеноиде
http://notabenoid.org/book/39472/145875
Переводчики: SauleSaulite, mmamkin
Настройки
Готово:
