Un código como este sucede a menudo:
l = []
while foo:
#baz
l.append(bar)
#qux
Esto es realmente lento si está a punto de agregar miles de elementos a su lista, ya que la lista tendrá que cambiar constantemente de tamaño para adaptarse a los nuevos elementos.
En Java, puede crear una ArrayList con una capacidad inicial. Si tiene alguna idea de cuán grande será su lista, será mucho más eficiente.
Entiendo que un código como este a menudo se puede refactorizar en una lista de comprensión. Sin embargo, si el ciclo for / while es muy complicado, esto no es factible. ¿Hay algún equivalente para nosotros los programadores de Python?
python
list
dictionary
initialization
Claudiu
fuente
fuente
Respuestas:
Resultados . (evalúe cada función 144 veces y promedie la duración)
Conclusión . Apenas importa.
La optimización prematura es la fuente de todos los males.
fuente
Las listas de Python no tienen preasignación incorporada. Si realmente necesita hacer una lista, y necesita evitar la sobrecarga de agregar (y debe verificar que lo haga), puede hacer esto:
Quizás podría evitar la lista utilizando un generador en su lugar:
De esta manera, la lista no se almacena todo en la memoria, simplemente se genera según sea necesario.
fuente
Versión corta: uso
para preasignar una lista (es decir, para poder abordar los elementos de "tamaño" de la lista en lugar de formarla gradualmente agregando). Esta operación es MUY rápida, incluso en grandes listas. La asignación de nuevos objetos que luego se asignarán a elementos de la lista llevará MUCHO más tiempo y será EL cuello de botella en su programa, en términos de rendimiento.
Versión larga:
Creo que se debe tener en cuenta el tiempo de inicialización. Dado que en Python todo es una referencia, no importa si establece cada elemento en None o alguna cadena, de cualquier manera es solo una referencia. Aunque llevará más tiempo si desea crear un nuevo objeto para que cada elemento haga referencia.
Para Python 3.2:
Evaluación:
Como puede ver, hacer una gran lista de referencias al mismo objeto None toma muy poco tiempo.
Pretender o extender toma más tiempo (no promedié nada, pero después de ejecutar esto varias veces puedo decirle que extender y agregar toma aproximadamente el mismo tiempo).
Asignar un nuevo objeto para cada elemento: eso es lo que lleva más tiempo. Y la respuesta de S.Lott hace eso: formatea una nueva cadena cada vez. Lo cual no es estrictamente necesario: si desea preasignar algo de espacio, simplemente haga una lista de Ninguno y luego asigne datos a los elementos de la lista a voluntad. De cualquier manera, lleva más tiempo generar datos que agregar / extender una lista, ya sea que la genere al crear la lista o después de eso. Pero si desea una lista escasamente poblada, comenzar con una lista de Ninguno es definitivamente más rápido.
fuente
[]*
enfoqueLa forma pitónica para esto es:
o cualquier valor predeterminado con el que desee completar, p. ej.
[EDITAR: Caveat Emptor La
[Beer()] * 99
sintaxis crea unoBeer
y luego llena una matriz con 99 referencias a la misma instancia]El enfoque predeterminado de Python puede ser bastante eficiente, aunque esa eficiencia disminuye a medida que aumenta el número de elementos.
Comparar
con
En mi Windows 7 i7, Python de 64 bits da
Mientras que C ++ da (construido con MSVC, 64 bits, optimizaciones habilitadas)
La compilación de depuración de C ++ produce:
El punto aquí es que con Python puede lograr una mejora del rendimiento del 7-8%, y si cree que está escribiendo una aplicación de alto rendimiento (o si está escribiendo algo que se utiliza en un servicio web o algo así) eso no debe ser rastreado, pero es posible que deba repensar su elección de idioma.
Además, el código de Python aquí no es realmente el código de Python. Cambiar a código verdaderamente Pythonesque aquí ofrece un mejor rendimiento:
Lo que da
(en 32 bits doGenerator funciona mejor que doAllocate).
Aquí la brecha entre doAppend y doAllocate es significativamente mayor.
Obviamente, las diferencias aquí solo se aplican si está haciendo esto más de un puñado de veces o si está haciendo esto en un sistema muy cargado donde esos números van a ser escalados por órdenes de magnitud, o si está tratando con Listas considerablemente más grandes.
El punto aquí: hágalo de la manera pitónica para obtener el mejor rendimiento.
Pero si le preocupa el rendimiento general de alto nivel, Python es el lenguaje incorrecto. El problema más fundamental es que las llamadas a funciones de Python han sido tradicionalmente hasta 300 veces más lentas que otros lenguajes debido a características de Python como decoradores, etc. ( https://wiki.python.org/moin/PythonSpeed/PerformanceTips#Data_Aggregation#Data_Aggregation ).
fuente
timeit
timeit
, que debe usar al cronometrar su código Python; No estoy hablando de C ++, obviamente.bottles = [Beer()] * 99
no crea 99 objetos Beer. En su lugar, crea un objeto Beer con 99 referencias a él. Si lo mutas, todos los elementos en la lista serán mutados, causa(bottles[i] is bootles[j]) == True
de cada unoi != j. 0<= i, j <= 99
.Como otros han mencionado, la forma más simple de pre-sembrar una lista con
NoneType
objetos.Dicho esto, debe comprender la forma en que funcionan realmente las listas de Python antes de decidir que esto es necesario. En la implementación de CPython de una lista, la matriz subyacente siempre se crea con espacio superior, en tamaños progresivamente más grandes
( 4, 8, 16, 25, 35, 46, 58, 72, 88, 106, 126, 148, 173, 201, 233, 269, 309, 354, 405, 462, 526, 598, 679, 771, 874, 990, 1120, etc)
, de modo que el cambio de tamaño de la lista no ocurre con tanta frecuencia.Debido a este comportamiento, la mayoría de las
list.append()
funciones sonO(1)
complejas para los anexos, solo que tienen una mayor complejidad al cruzar uno de estos límites, en cuyo punto la complejidad seráO(n)
. Este comportamiento es lo que conduce al aumento mínimo en el tiempo de ejecución en la respuesta de S. Lott.Fuente: http://www.laurentluce.com/posts/python-list-implementation/
fuente
ejecuté el código de @ s.lott y produje el mismo aumento de rendimiento del 10% mediante preasignación. Probé la idea de @Jeremy usando un generador y pude ver el rendimiento del gen mejor que el del doAllocate. Para mi proyecto, la mejora del 10% es importante, así que gracias a todos, ya que esto ayuda mucho.
fuente
Las preocupaciones sobre la preasignación en Python surgen si está trabajando con numpy, que tiene más matrices tipo C. En este caso, las preocupaciones previas a la asignación son sobre la forma de los datos y el valor predeterminado.
Considere numpy si está haciendo un cálculo numérico en listas masivas y desea rendimiento.
fuente
Para algunas aplicaciones, un diccionario puede ser lo que está buscando. Por ejemplo, en el método find_totient, me pareció más conveniente usar un diccionario ya que no tenía un índice cero.
Este problema también podría resolverse con una lista preasignada:
Siento que esto no es tan elegante y propenso a errores porque no estoy almacenando Ninguno, lo que podría arrojar una excepción si accidentalmente los uso incorrectamente, y porque necesito pensar en casos extremos que el mapa me permite evitar.
Es cierto que el diccionario no será tan eficiente, pero como otros han comentado, pequeñas diferencias en la velocidad no siempre valen riesgos de mantenimiento significativos .
fuente
Por lo que entiendo, las listas de python ya son bastante similares a ArrayLists. Pero si desea modificar esos parámetros, encontré esta publicación en la red que puede ser interesante (básicamente, solo cree su propia
ScalableList
extensión):http://mail.python.org/pipermail/python-list/2000-May/035082.html
fuente