2011-04-28 11 views
6

Estaba probando las velocidades de algunas maneras diferentes de hacer iteraciones complejas sobre algunos de mis datos, y encontré algo raro. Parece que tener una gran lista local para alguna función ralentiza considerablemente esa función, incluso si no está tocando esa lista. Por ejemplo, la creación de 2 listas independientes a través de 2 instancias de la misma función del generador es aproximadamente 2.5 veces más lenta la segunda vez. Si se elimina la primera lista antes de crear la segunda, ambos iteradores van en el mismo spee.La función de Python se ralentiza con la presencia de una lista grande

def f(): 
    l1, l2 = [], [] 
    for c1, c2 in generatorFxn(): 
     l1.append((c1, c2)) 
    # destroying l1 here fixes the problem 
    for c3, c4 in generatorFxn(): 
     l2.append((c3, c4)) 

Las listas terminan alrededor de 3,1 millones de artículos de largo cada uno, pero vi el mismo efecto con listas más pequeñas también. El primer ciclo for tarda unos 4,5 segundos en ejecutarse, el segundo tarda 10,5. Si inserto l1= [] o l1= len(l1) en la posición de comentario, ambos bucles for tardan 4.5 segundos.

¿Por qué la velocidad de asignación de memoria local en una función tiene algo que ver con el tamaño actual de las variables de esa función?

EDITAR: Al deshabilitar el recolector de basura arregla todo, por lo que debe ser debido a que se ejecuta constantemente. ¡Caso cerrado!

Respuesta

9

Cuando crea tantos objetos nuevos (3 millones de tuplas), el recolector de basura se empantana. Si desactiva la recolección de basura con gc.disable(), el problema desaparece (y el programa se ejecuta 4 veces más rápido para arrancar).

+0

Correcto, señor, deshabilitarlo cae 4.5 segundos a 1.3 segundos, y casi elimina las diferencias (el segundo sigue siendo un poco más lento, pero no mucho más). ¿Por qué el recolector de basura funciona tan lento incluso después de una gran lista y solo existe, no se modifica? ¿No debería tener solo un trabajo que hacer una vez que la función regrese? – DaveTheScientist

+0

Además, ¿por qué desaparece la desaceleración si 'l' es una variable de clase en lugar de una variable local? – DaveTheScientist

+0

Aquí está mi mejor estimación: el recolector de basura se ejecuta periódicamente para recolectar objetos viejos. Determina cuándo ejecutar buscando comparando el número de objetos asignados y desasignados desde la última colección. Si el umbral asignado-desasignado>, el recopilador se ejecuta (y el umbral = 700 por defecto). Como crea (al menos) 1 objeto nuevo por iteración, el recopilador ejecuta 3e6/700 = 4285 veces. En realidad, esto ralentiza ambas iteraciones, pero la segunda iteración es más lenta ya que hay más objetos para que el recopilador los compruebe. – Luke

0

La memoria utilizada por los datos locales de la función no se recolectará basura hasta que la función regrese. A menos que tenga la necesidad de cortar, usar listas para grandes colecciones de datos no es una gran idea.

De su ejemplo, no está del todo claro cuál es el propósito de crear estas listas. Es posible que desee considerar el uso de generadores en lugar de listas, especialmente si las listas solo se van a repetir. Si necesita hacer un corte en los datos de retorno, envíe los generadores a las listas en ese momento.

+0

La creación de estas listas se hizo inicialmente para probar la velocidad de diferentes funciones del generador, pero tendré que crear listas como estas una vez que encuentre un algoritmo apropiado. La pregunta no es sobre encontrar una mejor manera de almacenar los datos, sino más bien sobre por qué hay un golpe de rendimiento tan drástico. No tiene sentido para mí, y me gustaría tener la respuesta en mente cuando programe en el futuro. Y sé que la recolección de basura no ocurrirá en el medio de la función, por lo que la reasignación de 'l' no debería solucionar el problema (y sin embargo lo hace). – DaveTheScientist

+0

¡Gracias por explicarme! – jathanism

2

Es imposible decirlo sin una instrumentación más detallada.

Como paso muy, muy preliminar, verifique el uso de la memoria principal. Si su RAM está llena y su sistema operativo está buscando en el disco, su rendimiento será bastante terrible. En tal caso, es mejor que tomes tus productos intermedios y los pongas en otro lugar que no sea memoria. Si solo necesita lecturas secuenciales de sus datos, considere escribir en un archivo simple; si sus datos siguen una estructura estricta, considere continuar en una base de datos relacional.

+1

Cada elemento de la lista es una tupla de 2 cadenas, cada una de 3 caracteres. Y he visto el mismo efecto con listas más pequeñas; con un tamaño de lista de 770,000 cada uno, los tiempos son 0.57 y 0.96 segundos. En 188,000 son 0.11 y 0.15 segundos. Estoy bastante seguro de que no es un umbral de memoria que estoy cruzando, ya que estoy en un sistema de 64 bits con 4 gb de ram. – DaveTheScientist

2

Supongo que cuando se hace la primera lista, hay más memoria disponible, lo que significa menos posibilidades de que la lista deba ser reasignada a medida que crece.

Después de ocupar un buen trozo de memoria con la primera lista, su segunda lista tiene una probabilidad mayor de de necesitar reasignarse a medida que crece, ya que las listas de pitones tienen un tamaño dinámico.

+0

+ 1 por tener mucho sentido y recordarme que la asignación de memoria (normalmente) no está en 1 gran parte. De acuerdo con el Monitor de actividad, ambas listas juntas requerían alrededor de 280 MB de espacio, por lo que es un buen pedazo. – DaveTheScientist

Cuestiones relacionadas