Como algunos habréis notado, siento cierta afición por Python. Ésto es así porque programar en Python es (por lo general) mucho más divertido que programar con la mayoría de lenguajes existentes. Aún así tengo ciertos problemas de conciencia al respecto, Python es un lenguaje interpretado y eso se nota en el rendimiento de los programas que se hacen con él. Yo recomendaría Python para algunas cosas muy concretas: aprendizaje, prototipado, usarlo como “lenguaje pegamento” para unir programas o librerías hechas en otros lenguajes más eficientes, o por último, como lenguaje de scripting del sistema (mucho mejor que Bash o Csh).

Aun así, puede darse el caso de que nos dé la gana de hacer una aplicación de uso común en la que sería deseable que el rendimiento fuera bueno, lo que no será posible si no conocemos algunos detalles de Python. Hoy comentaré como hacer bucles lo más rápidos posible.

En principio tenemos 2 formas de crear bucles en Python, con el bucle for y con el bucle while. El bucle for ejecuta un bloque de código por cada elemento de un objeto iterable.

L = [1 2 3 4]
for i in L:
  accion(i)

Mientras que el bucle while ejecuta un trozo de código mientras se cumpla una condición concreta:

i = 1
while i <= 4:
  accion(i)
  i += 1

Los objetos iterables pueden ser de muchos tipos, pueden ser listas (que consumen memoria por cada uno de sus elementos) o pueden ser también generadores, objetos que devuelven un elemento nuevo cada vez que se llama a su método __iter__, también se pueden crear métodos generadores que actúan como objetos iterables. Así que podemos crear un método que hará algo parecido a la función range:

def generador(n):
  i = 0
  while i < n:
    yield i
    i += 1

Que podemos usar en un bucle:

  for i in generador(5):
    print i

Nota: por si alguien no sabe lo que hace la función range, crea una lista de 0 a n y la devuelve (donde n es el parámetro que se le pasa).

¿Qué ventajas tienen los generadores sobre la función range? Pues para empezar, no consumen tanta memoria, no hace falta crear un array de n elementos para hacer un bucle con n iteraciones. Aun así, si experimentamos con nuestros generadores y comparamos tiempos de ejecución veremos que la función range funciona significativamente más rápida. La razón es que está programada internamente en C y nuestro generador está programado en Python.

Ésto nos puede hacer pensar que la mejor alternativa, entonces, es usar un bucle while (aunque sea más feo) ya que evitaremos la sobrecarga de llamar a una función, no gastaremos tanta memoria como con range y sólo haremos una comparación al principio y una suma al final de cada iteración. Pero... nos equivocaríamos otra vez. Resulta que la función range da mejores resultados que el típico bucle while en cuanto a tiempo, la razón, otra vez, es que está programada internamente en C. De hecho es esperable que si el array creado por range fuera lo suficientemente grande, los resultados cambiaran (porque se tendría que recurrir a la memoria virtual y habría fallos de página), pero para que eso se dé hacen falta muuchas iteraciones, sobretodo ahora, cuando tenemos tanta memoria RAM.

Después de todo, podemos acabar un poco desanimados, pues no hemos visto ninguna solución que nos aporte todo lo que queremos. Pero está ahí :) , y se llama xrange. Funciona de manera análoga a la función range, sólo que en vez de devolvernos un array lo que hace es devolvernos un generador sobre el que podrá iterar el bucle for de forma elegante y aprovechando el rendimiento de C (pues está programada internamente en C). Así pues, la manera más eficiente de hacer un bucle viene a ser algo como ésto:

for i in xrange(5):
  print i

No pongo aquí los tiempos porque haría demasiado largo el artículo y lo podéis calcular vosotros mismos de forma fácil con el comando time de UNIX. Saludos!

Comparte y disfruta:
  • Print
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Bitacoras.com
  • Identi.ca
  • Meneame
  • StumbleUpon
  • Technorati
  • Twitter
votar

« »