2012-09-25 24 views
32

Creo que puedo haber implementado esto incorrectamente porque los resultados no tienen sentido. Tengo un programa ir que cuenta hasta 1000000000¿Puede ir realmente mucho más rápido que Python?

package main 

    import (
     "fmt" 
    ) 

    func main() { 
     for i := 0; i < 1000000000; i++ {} 
     fmt.Println("Done") 
    } 

Termina en menos de un segundo. Por otro lado, tengo un script de python

x = 0 
    while x < 1000000000: 
     x+=1 
    print 'Done' 

Termina en unos minutos.

¿Por qué es la versión Ir mucho más rápido. ¿Ambos cuentan hasta 1000000000 o me estoy perdiendo algo?

Respuesta

66

Mil millones no es un número muy grande. Cualquier máquina razonablemente moderna debe ser capaz de hacer esto en unos pocos segundos como máximo, si es capaz de hacer el trabajo con los tipos nativos. Lo verifiqué escribiendo un programa C equivalente, leyendo el ensamblaje para asegurarme de que realmente estaba haciendo una adición y cronometrando (se completa en aproximadamente 1,8 segundos en mi máquina).

Python, sin embargo, no tiene un concepto de variables de tipo nativo (o anotaciones de tipo significativo en absoluto), por lo que tiene que hacer cientos de veces tanto trabajo en este caso. En resumen, la respuesta a su pregunta principal es "sí". Ir muy puede ser mucho más rápido que Python, incluso sin ningún tipo de engaño compilador como la optimización de distancia un bucle secundario de libre efecto.

-1

No estoy familiarizado con el movimiento, pero supongo que van versión ignora el bucle ya que el cuerpo del bucle no hace nada. Por otro lado, en la versión de Python, está incrementando x en el cuerpo del bucle, por lo que probablemente esté ejecutando el bucle.

+0

Cambié el bucle for para asignar i a otra variable (es decir, i2 = i) para cada bucle y la velocidad seguía siendo la misma (así que básicamente sé que se ejecuta el bucle for). – bab

+0

Tenía el programa de impresión i2 al final, e i2 era 999999999 – bab

15

Este escenario favorecer altamente decente compiladas de forma nativa-lenguajes de tipo estático. Los lenguajes de tipo estático compilados de manera nativa son capaces de emitir un bucle muy trivial de, digamos, 4-6 códigos de operación de CPU que utiliza una condición de verificación simple para la terminación. Este bucle tiene efectivamente cero pierde predicción de saltos y puede ser pensado como la realización de eficacia de un incremento cada ciclo de la CPU (esto no es del todo cierto, pero ..)

implementaciones de Python tienen que hacer el trabajo significativamente más , principalmente debido a la escritura dinámica. Python debe realizar varias llamadas diferentes (internas y externas) solo para agregar dos int s juntos. En Python es debe llamar__add__ (es efectivamente i = i.__add__(1), pero esta sintaxis solo funcionará en Python 3.x), que a su vez tiene que verificar el tipo del valor pasado (para asegurarse de que es int), luego agrega los valores enteros (extrayéndolos de los objetos), y luego el nuevo valor entero se envuelve nuevamente en un nuevo objeto. Finalmente, reasigna el nuevo objeto a la variable local. Eso es significativamente más trabajo que un solo código de operación para incrementar, y ni siquiera se ocupa del bucle en sí; en comparación, la versión Go/native probablemente solo incremente un registro por efecto secundario.

Java será justo mucho mejor en un punto de referencia trivial como este y probablemente estará bastante cerca de Go; el JIT y tipado estático de la variable de contador puede garantizar esto (utiliza una instrucción JVM de suma entera especial). Una vez más, Python no tiene esa ventaja. Ahora, hay algunas implementaciones como PyPy/RPython, que se ejecutan en una fase tipificación estática y debe favorecer mucho mejor que CPython aquí ..

+6

No quise utilizar esto como punto de referencia (lo siento si no lo dejé claro). Me preguntaba por qué la versión de Python era mucho más lenta. – bab

+0

-1: Su comentario final "inherentemente engañoso" parece ser solo como una afirmación sin justificación o explicación. – igouy

+1

@igouy No entiendo cómo fue injustificado (toda la publicación fue una justificación), pero la eliminé porque no agregaba nada nuevo. –

-1

Es posible que el compilador se dio cuenta de que no ha utilizado la variable "i" después de la loop, por lo que optimizó el código final eliminando el bucle.

Incluso si lo utilizó después, el compilador es probablemente lo suficientemente inteligente como para sustituir el lazo con

i = 1000000000; 

Espero que esto ayude =)

+0

Puede verificar que el ciclo esté aún en el código obteniendo el listado del ensamblador: 'go build -gcflags -S main.go' – topskip

8

Tienes dos cosas en el trabajo aquí. El primero de ellos es que Go se compila con código de máquina y se ejecuta directamente en la CPU, mientras que Python se compila para ejecutar bytecode en una máquina virtual (particularmente lenta).

El segundo, y más importante, lo que afecta el rendimiento es que la semántica de los dos programas son en realidad muy diferente. La versión Go hace una "caja" llamada "x" que contiene un número e incrementa eso en 1 en cada pase a través del programa. La versión de Python en realidad tiene que crear un nuevo "cuadro" (objeto int) en cada ciclo (y, eventualmente, tiene que tirarlos). Podemos demostrar esto modificando ligeramente sus programas:

package main 

import (
    "fmt" 
) 

func main() { 
    for i := 0; i < 10; i++ { 
     fmt.Printf("%d %p\n", i, &i) 
    } 
} 

... y:

x = 0; 
while x < 10: 
    x += 1 
    print x, id(x) 

Esto es debido a Go, debido a sus raíces C, toma un nombre de variable para hacer referencia a un lugar , donde Python toma nombres de variables para referirse a cosas. Como un entero se considera una entidad única e inmutable en Python, debemos crear constantemente nuevos. Python debe ser más lento que Go, pero ha escogido el peor de los casos: in the Benchmarks Game, vemos que va, en promedio, unas 25 veces más rápido (100 veces en el peor de los casos).

Probablemente hayas leído que, si tus programas Python son demasiado lentos, puedes acelerarlos moviendo cosas a C. Afortunadamente, en este caso, alguien ya lo hizo por ti. Si se vuelve a escribir su bucle vacío para usar xrange() así:

for x in xrange(1000000000): 
    pass 
print "Done." 

... verá correr el doble de rápido. Si encuentra que los contadores de bucles son realmente un cuello de botella importante en su programa, podría ser hora de investigar una nueva forma de resolver el problema.

+1

Es mejor usar la comparación directa de juegos de pruebas entre Python y Go - http: // shootout. alioth.debian.org/u64q/benchmark.php?test=all&lang=go&lang2=python3 - Parece que se ha confundido y ha informado de cómo el rendimiento de Python se compara con C. – igouy

50

PyPy realmente hace un trabajo impresionante de acelerar este bucle

def main(): 
    x = 0 
    while x < 1000000000: 
     x+=1 

if __name__ == "__main__": 
    s=time.time() 
    main() 
    print time.time() - s 

$ python count.py 
44.221405983 
$ pypy count.py 
1.03511095047 

~ 97% más veloz!

Aclaración para 3 personas que no "hacer las cosas".El lenguaje Python en sí no es lento. La implementación de CPython es una forma relativamente sencilla de ejecutar el código. Pypy es otra implementación del lenguaje que hace muchas cosas complicadas (especialmente el JAT) que pueden hacer enormes diferencias. Responder directamente a la pregunta en el título - Ir no es "mucho" más rápido que Python, Ir es mucho más rápido que CPython.

Habiendo dicho eso, las muestras de código realmente no están haciendo lo mismo. Python necesita instanciar 1000000000 de sus objetos int. Go solo está incrementando una ubicación de memoria.

0

@troq

Estoy un poco tarde a la fiesta, pero yo diría que la respuesta es sí y no. Como señaló @gnibbler, CPython es más lento en la implementación simple, pero pypy se compila para un código mucho más rápido cuando lo necesita.

Si está haciendo un procesamiento numérico con CPython, la mayoría lo hará con numpy, lo que dará como resultado operaciones rápidas en matrices y matrices. Recientemente he estado haciendo mucho con Numba, que te permite agregar un envoltorio simple a tu código. Para este, acabo de agregar @njit a una función incALot() que ejecuta el código anterior.

En mi máquina CPython lleva 61 segundos, pero con el envoltorio numba se necesitan 7,2 microsegundos, que serán similares a C y quizás más rápidos que Go. Eso es una aceleración de 8 millones de veces.

Por lo tanto, en Python, si las cosas con números parecen un poco lentas, hay herramientas para abordarlas, y aún así obtiene la productividad del programador de Python y el REPL.

def incALot(y): 
    x = 0 
    while x < y: 
     x += 1 

@njit('i8(i8)') 
def nbIncALot(y): 
    x = 0 
    while x < y: 
     x += 1 
    return x 

size = 1000000000 
start = time.time() 
incALot(size) 
t1 = time.time() - start 
start = time.time() 
x = nbIncALot(size) 
t2 = time.time() - start 
print('CPython3 takes %.3fs, Numba takes %.9fs' %(t1, t2)) 
print('Speedup is: %.1f' % (t1/t2)) 
print('Just Checking:', x) 

CPython3 takes 58.958s, Numba takes 0.000007153s 
Speedup is: 8242982.2 
Just Checking: 1000000000 
0

El problema es que Python se interpreta, GO no es así, no hay manera de que las velocidades de prueba sean reales. Generalmente, los lenguajes interpretados (no siempre tienen un componente vm) es donde reside el problema, cualquier prueba que ejecute se está ejecutando en límites interpretados, no en límites de tiempo de ejecución reales. Go es ligeramente más lento que C en términos de velocidad y eso se debe principalmente a que usa recolección de basura en lugar de gestión de memoria manual. Dicho esto, GO en comparación con Python es rápido porque es un lenguaje compilado, lo único que falta en GO es la prueba de errores. Me corrigen si me equivoco.

Cuestiones relacionadas