2010-04-10 22 views
11

¿Cuál es la lógica detrás del uso defendido de las construcciones de bucle de estilo for i in xrange(...) en Python? Para el bucle entero simple, la diferencia en los gastos generales es considerable. Llevé a cabo una prueba sencilla utilizando dos piezas de código:Justificación de Python preferido para la sintaxis

archivos idiomatic.py:

#!/usr/bin/env python 

M = 10000 
N = 10000 

if __name__ == "__main__": 
    x, y = 0, 0 
    for x in xrange(N): 
     for y in xrange(M): 
      pass 

Archivo cstyle.py:

#!/usr/bin/env python 

M = 10000 
N = 10000 

if __name__ == "__main__": 
    x, y = 0, 0 
    while x < N: 
     while y < M: 
      y += 1 
     x += 1 

Profiling resultados fueron los siguientes:

bash-3.1$ time python cstyle.py 

real 0m0.109s 
user 0m0.015s 
sys  0m0.000s 

bash-3.1$ time python idiomatic.py 

real 0m4.492s 
user 0m0.000s 
sys  0m0.031s 

que pueda entender por qué la versión Pythonic es más lenta, me lo imagino tiene mucho que ver con llamar a xrange N veces, quizás esto podría eliminarse si hubiera una forma de rebobinar un generador. Sin embargo, con esta gran diferencia en el tiempo de ejecución, ¿por qué uno preferiría usar la versión Pythonic?

Editar: que lleva a cabo las pruebas de nuevo utilizando el código proporcionado el Sr. Martelli, y los resultados eran de hecho mejor ahora:

que pensé en enumerar las conclusiones de ese tema aquí:

1) Un montón de código en el alcance del módulo es una mala idea,, incluso si el código está encerrado en un bloque if __name__ == "__main__":.

2) * Curiosamente, la modificación del código que pertenecía a thebadone a mi versión incorrecta (dejando crecer y sin restablecer) producido poca diferencia en el rendimiento, incluso para valores grandes de M y N.

+2

Su tiempo es defectuoso, creo. Ejecute varias pruebas, y tal vez realice algunos cálculos en su interior para deshacerse de cualquier posible optimización fuera del ciclo – Yuliy

+0

+1 Pregunta muy interesante. Después de leer la respuesta de Martinelli, esta pregunta es aún más interesante para mí, porque muestra las diferencias sutiles entre invocar un fragmento de código dentro y fuera de una función. – OscarRyz

+1

-1: Como la base de la pregunta era un código fundamentalmente incorrecto, ¿podría cerrar la pregunta? –

Respuesta

22

Aquí está la comparación adecuada, p. en loop.py:

M = 10000 
N = 10000 

def thegoodone(): 
    for x in xrange(N): 
     for y in xrange(M): 
      pass 

def thebadone(): 
    x = 0 
    while x < N: 
     y = 0 
     while y < M: 
      y += 1 
     x += 1 

Todo el código debe sustancial siempre estar en funciones - poner cien millones de bucles en el nivel superior de un módulo de muestra total indiferencia hacia el rendimiento y se burla de cualquier intento de medir dicho rendimiento

Una vez que haya hecho esto, ver:

$ python -mtimeit -s'import loop' 'loop.thegoodone()' 
10 loops, best of 3: 3.45 sec per loop 
$ python -mtimeit -s'import loop' 'loop.thebadone()' 
10 loops, best of 3: 10.6 sec per loop 

Así, medido correctamente, la mala manera que defendemos es aproximadamente 3 veces más lenta que la buena forma que promueve Python. Espero que esto te haga reconsiderar tu defensa errónea.

+4

"Todo código importante debe estar siempre en funciones: poner cien millones de bucles en el nivel superior de un módulo muestra despreocupación imprudente por el rendimiento" (ya veo). ¿Podría explicar por qué? –

+6

@Federico, ** velocidad **. El conjunto y obtención de variables está altamente optimizado en funciones, pero no puede estar en el nivel superior del módulo. Por ejemplo, suponiendo que mi computadora portátil y la máquina de Glenn son equivalentes, de nuestros números ve un factor de diferencia de 2 para hacer las cosas de la manera correcta (todo código sustancial en funciones) frente a la forma totalmente incorrecta (código sustancial en el nivel superior del módulo). ¡Hágalo usted mismo! -) –

+1

Acabo de hacerlo. A nivel de módulo: 14.3 seg vs. 36.0 seg. Local para funcionar: 8.6 s contra 18.5 s. (!!) No lo sabía, gracias. –

11

Usted se olvidó de reinicie y a 0 después del bucle interno.

#!/usr/bin/env python 
M = 10000 
N = 10000 

if __name__ == "__main__": 
    x, y = 0, 0 
    while x < N: 
     while y < M: 
      y += 1 
     x += 1 
     y = 0 

ed: 20.63s después vs. solución 6.97s usando xrange

+0

Estaba a punto de responder de manera similar. Mi código corregido while-loop corrió aproximadamente 3 veces más lento que el código for-loop. –

+1

Con toda seriedad, no. Los idiomas idiomáticos siempre deben tener en cuenta el rendimiento razonable; si el idioma de xrange en realidad * era * 40 veces más lento, sería un modismo defectuoso que debería ser reparado o que ya no se usa.La legibilidad es importante, a menudo incluso a expensas de algún rendimiento, pero no tanto. –

+0

OK eliminé mi respuesta de todos modos ... –

3

bueno para iterar sobre las estructuras de datos

La sintaxis for i in ... es ideal para iterar sobre las estructuras de datos. En un lenguaje de nivel inferior, generalmente estaría iterando sobre una matriz indexada por un int, pero con la sintaxis de python puede eliminar el paso de indexación.

0

He repetido la prueba de @Alex Martelli's answer. El bucle for es idiomática 5 veces más rápido que el bucle while:

python -mtimeit -s'from while_vs_for import while_loop as loop' 'loop(10000)' 
10 loops, best of 3: 9.6 sec per loop 
python -mtimeit -s'from while_vs_for import for_loop as loop' 'loop(10000)' 
10 loops, best of 3: 1.83 sec per loop 

while_vs_for.py:

def while_loop(N): 
    x = 0 
    while x < N: 
     y = 0 
     while y < N: 
      pass 
      y += 1 
     x += 1 

def for_loop(N): 
    for x in xrange(N): 
     for y in xrange(N): 
      pass 

A nivel de módulo:

$ time -p python for.py 
real 4.38 
user 4.37 
sys 0.01 
$ time -p python while.py 
real 14.28 
user 14.28 
sys 0.01 
1

esto no es una respuesta directa a la pregunta, pero quiero abrir el diálogo un poco más en xrange(). dos cosas:

A. hay algo mal con uno de los estados OP que nadie ha corregido todavía (sí, además del error en el código de no restablecer y):

"Me imagino que tiene mucho que ver con llamar xrange N veces ...."

a diferencia de conteo tradicional for bucles, Python es más como de foreach una concha ... bucle sobre un iterable. por lo tanto, xrange() se llama exactamente una vez, no "N veces".

B. xrange() es el nombre de esta función en Python 2. reemplaza y se renombra a range() en Python 3, así que ten esto en cuenta al realizar la transferencia. si aún no lo sabía, xrange() devuelve un iterador (-objeto similar) mientras que range() devuelve listas. ya que este último es más ineficiente, se ha desaprobado a favor de xrange(), que es más fácil de usar con la memoria. la solución en Python 3, para todos aquellos que necesitan para tener una lista es list(range(N)).

+0

Pensé que cada vez que el ciclo interno terminaba todas sus iteraciones, se definía nuevamente un objeto xrange (M), ya que los generadores AFAIK no podían rebobinarse. – susmits

Cuestiones relacionadas