Подтвердить что ты не робот

Почему Python и Ruby настолько медленны, а реализация Lisp выполняется быстро?

Я нахожу, что простые вещи, такие как вызовы функций и циклы, и даже просто циклы, увеличивающие счетчик, занимают far больше времени в Python и Ruby, чем в Chicken Scheme, Racket или SBCL.

Почему это так? Я часто слышу, как люди говорят, что медленность - это цена, которую вы платите за динамические языки, но Lisps очень динамичны и не смехотворно медленны (они обычно меньше, чем в 5 раз медленнее C, Ruby и Python могут входить в двойные цифры). Кроме того, стиль Lisp использует рекурсию, а не всегда хвостовую рекурсию, много, стек - это связанный список продолжений в куче и т.д., Которые, похоже, являются вещами, которые должны сделать Lisp медленнее, чем Python в стиле императивного стиля и Ruby.

Racket и SBCL JITted, но Chicken Scheme либо статически компилируется, либо использует не оптимизирующий интерпретатор, оба из которых должны быть плохо подходят для динамических языков и медленны. Тем не менее, даже используя наивный интерпретатор csi для Chicken Scheme (который даже не делает компиляцию байт-кода!), Я получаю скорости далеко за пределами Python и Ruby.

Почему именно Python и Ruby настолько смехотворно медленны по сравнению с аналогично динамическими Lisps? Это потому, что они объектно ориентированы и нужны огромные vtables и типы heirarchies?

Пример: факториальная функция. Python:

def factorial(n):
    if n == 0:
        return 1
    else:
    return n*factorial(n-1)

for x in xrange(10000000):
    i = factorial(10)

Ракетка:

#lang racket

(define (factorial n)
  (cond
   [(zero? n) 1]
   [else (* n (factorial (sub1 n)))]))

(define q 0)

(for ([i 10000000])
  (set! q (factorial 10)))

Результаты синхронизации:

[email protected] /scratch> time racket factorial.rkt
racket factorial.rkt  1.00s user 0.03s system 99% cpu 1.032 total
[email protected] /scratch> time python factorial.py
python factorial.py  13.66s user 0.01s system 100% cpu 13.653 total
4b9b3361

Ответ 1

Скомпилированные системы Lisp, как правило, довольно быстро, чем Ruby или Python.

См., например, сравнение Ruby и SBCL:

http://benchmarksgame.alioth.debian.org/u32/benchmark.php?test=all&lang=yarv&lang2=sbcl&data=u32

или Python и SBCL:

http://benchmarksgame.alioth.debian.org/u32/benchmark.php?test=all&lang=python3&lang2=sbcl&data=u32

Но имейте в виду следующее:

  • SBCL использует собственный компилятор кода. Он не использует байт-кодовую машину или что-то вроде компилятора JIT из байтового кода в собственный код. SBCL компилирует весь код из исходного кода в собственный код, перед выполнением. Компилятор является инкрементным и может компилировать отдельные выражения. Таким образом, он используется также функцией EVAL и из Read-Eval-Print-Loop.
  • SBCL использует оптимизирующий компилятор, который использует объявления типа и вывод типа. Компилятор генерирует собственный код.
  • Общие Lisp допускают различные оптимизации, которые делают код менее динамичным или не динамическим (вложение, раннее связывание, проверка типа, код, специализированный для объявленных типов, оптимизация хвостового вызова,...). Код, который использует эти расширенные функции, может выглядеть сложным - особенно когда компилятору нужно рассказать об этом.
  • Без этих оптимизаций скомпилированный Lisp код все же быстрее, чем интерпретируемый код, но медленнее, чем оптимизированный скомпилированный код.
  • Common Lisp предоставляет CLOS, общую Lisp систему объектов. CLOS-код обычно медленнее, чем не CLOS, где это сравнение имеет смысл. Динамический функциональный язык имеет тенденцию быть быстрее, чем динамический объектно-ориентированный язык.
  • Если в реализации языка используется высоко оптимизированная среда выполнения, например, для арифметических операций bignum, медленная реализация языка может быть быстрее, чем оптимизирующий компилятор. Некоторые языки имеют множество сложных примитивов, внедренных в C. Они, как правило, быстрые, в то время как остальная часть языка может быть очень медленной.

Также некоторые операции могут выглядеть похожими, но могут отличаться. Является ли цикл for итерацией по целочисленной переменной по сути такой же, как цикл for, который выполняет итерацию в диапазоне?

Ответ 2

Отправка метода в Ruby/Python/etc стоит дорого, а программы Ruby/Python/etc вычисляются в основном методом вызова. Даже теги for в Ruby - это просто синтаксический сахар для вызова метода each.

Ответ 3

Я не знаю о вашей установке racket, но Racket я просто apt-get install 'd использует компиляцию JIT, если она выполняется без флагов. Запуск с --no-jit дает время гораздо ближе к времени Python (racket: 3s, racket --no-jit: 37s, python: 74s). Кроме того, назначение в области модуля медленнее, чем локальное назначение в Python по причинам дизайна языка (очень либеральная модульная система), перемещение кода в функцию ставит Python на 60 секунд. Оставшийся пробел, вероятно, можно объяснить как комбинацию совпадений, различные фокусы оптимизации (вызовы функций должны быть безумно быстрыми в Lisp, люди Python меньше заботятся), качество реализации (ref-counting по сравнению с соответствующим GC, стек VM против регистра VM) и т.д., А не фундаментальное следствие соответствующих языковых конструкций.

Ответ 4

Я думаю, что вместо того, чтобы сам Python был медленным, он интерпретирует интерпретатор Python через код медленнее. Если вы попытались скомпилировать код с помощью инструмента, такого как py2exe, то это может быть быстрее, чем lisp. Вы должны попробовать, но я думаю, что у него просто медленный переводчик.