python - sirve - ruby tutorial
¿Por qué Python y Ruby son tan lentos, mientras que las implementaciones de Lisp son rápidas? (4)
Creo que en lugar de que Python sea lento en sí mismo, es el intérprete de Python que se mueve a través del código a un ritmo más lento. Si intentaste compilar el código con una herramienta como py2exe, entonces puede ser más rápido que lisp. Tendrías que probarlo, pero creo que solo tiene un intérprete lento.
Encuentro que cosas simples como llamadas a funciones y bucles, e incluso solo los bucles que incrementan un contador llevan mucho más tiempo en Python y Ruby que en Chicken Scheme, Racket o SBCL.
¿Por qué esto es tan? A menudo escucho a la gente decir que la lentitud es un precio que pagas por los lenguajes dinámicos, pero los Lisps son muy dinámicos y no son ridículamente lentos (por lo general son menos de 5 veces más lentos que C; Ruby y Python pueden ir en dos dígitos). Además, el estilo Lisp usa la recursión, y no siempre la cola, mucho, la pila es una lista vinculada de continuaciones en el montón, etc., que parecen ser cosas que deberían hacer a Lisp más lento que el estilo imperativo de Python y Ruby.
Racket y SBCL están JITted, pero Chicken Scheme se compila de forma estática o utiliza un intérprete que no optimiza, ambos deberían ser muy adecuados para los lenguajes dinámicos y lentos. Sin embargo, incluso utilizando el intérprete csi
ingenuo para Chicken Scheme (¡que ni siquiera hace la compilación del código de bytes!), Obtengo velocidades mucho más allá de Python y Ruby.
¿Por qué exactamente Python y Ruby son ridículamente lentos en comparación con los Lisps de dinámica similar? ¿Es porque están orientados a objetos y necesitan enormes vtables y jerarquías de tipo?
Ejemplo: función factorial. Pitón:
def factorial(n):
if n == 0:
return 1
else:
return n*factorial(n-1)
for x in xrange(10000000):
i = factorial(10)
Raqueta:
#lang racket
(define (factorial n)
(cond
[(zero? n) 1]
[else (* n (factorial (sub1 n)))]))
(define q 0)
(for ([i 10000000])
(set! q (factorial 10)))
Resultados de tiempo:
ithisa@miyasa /scratch> time racket factorial.rkt
racket factorial.rkt 1.00s user 0.03s system 99% cpu 1.032 total
ithisa@miyasa /scratch> time python factorial.py
python factorial.py 13.66s user 0.01s system 100% cpu 13.653 total
El envío de métodos en Ruby / Python / etc es costoso, y los programas de Ruby / Python / etc se computan principalmente mediante métodos de llamada. Incluso for
bucles en Ruby son solo azúcar sintáctica para un método llamado a each
.
Los sistemas compilados de Lisp suelen ser un poco más rápidos que Ruby o Python.
Vea por ejemplo una comparación de Ruby y SBCL:
http://benchmarksgame.alioth.debian.org/u32/benchmark.php?test=all&lang=yarv&lang2=sbcl&data=u32
o Python y SBCL:
http://benchmarksgame.alioth.debian.org/u32/benchmark.php?test=all&lang=python3&lang2=sbcl&data=u32
Pero ten en cuenta lo siguiente:
- SBCL utiliza un compilador de código nativo. No utiliza una máquina de código de bytes o algo así como un compilador JIT de código de bytes a código nativo. SBCL compila todo el código del código fuente al código nativo, antes del tiempo de ejecución. El compilador es incremental y puede compilar expresiones individuales. Por lo tanto, se utiliza también por la función EVAL y desde el Read-Eval-Print-Loop.
- SBCL utiliza un compilador de optimización que hace uso de declaraciones de tipo e inferencia de tipo. El compilador genera código nativo.
- Common Lisp permite varias optimizaciones que hacen que el código sea menos dinámico o no dinámico (en línea, enlace anticipado, sin verificación de tipos, código especializado para tipos declarados, optimizaciones de llamadas de cola, ...). El código que hace uso de estas funciones avanzadas puede parecer complicado, especialmente cuando el compilador necesita que se le informe sobre estas cosas.
- Sin estas optimizaciones, el código Lisp compilado es aún más rápido que el código interpretado, pero más lento que el código compilado optimizado.
- Common Lisp proporciona CLOS, el sistema de objetos Common Lisp. El código CLOS generalmente es más lento que el no CLOS, donde esta comparación tiene sentido. Un lenguaje funcional dinámico tiende a ser más rápido que un lenguaje dinámico orientado a objetos.
- Si una implementación de lenguaje utiliza un tiempo de ejecución altamente optimizado, por ejemplo para operaciones aritméticas bignum, una implementación de lenguaje lenta puede ser más rápida que un compilador de optimización. Algunos lenguajes tienen muchos primitivos complejos implementados en C. Estos tienden a ser rápidos, mientras que el resto del lenguaje puede ser muy lento.
También algunas operaciones pueden parecer similares, pero podrían ser diferentes. ¿Es un bucle for
iterar sobre una variable entera realmente lo mismo que un bucle for
que itera sobre un rango?
No sé sobre la instalación de su raqueta, pero la Raqueta que acabo apt-get install
la compilación JIT si se ejecuta sin marcas. Correr con --no-jit
da un tiempo mucho más cercano al tiempo de Python ( racket
: 3s, racket --no-jit
jit: 37s, python
: 74s). Además, la asignación en el alcance del módulo es más lenta que la asignación local en Python por razones de diseño de lenguaje (sistema de módulos muy liberal), mover el código a una función pone a Python en los 60 s. La brecha restante probablemente se puede explicar como una combinación de coincidencia, un enfoque de optimización diferente (las llamadas a funciones deben ser increíblemente rápidas en Lisp, a la gente de Python le importa menos), la calidad de la implementación (recuento de refacciones versus GC adecuado, VM de pila contra VM de registro) , etc. más que una consecuencia fundamental de los respectivos diseños lingüísticos.