Esperaba array.array
ser más rápido que las listas, ya que las matrices parecen estar sin caja.
Sin embargo, obtengo el siguiente resultado:
In [1]: import array
In [2]: L = list(range(100000000))
In [3]: A = array.array('l', range(100000000))
In [4]: %timeit sum(L)
1 loop, best of 3: 667 ms per loop
In [5]: %timeit sum(A)
1 loop, best of 3: 1.41 s per loop
In [6]: %timeit sum(L)
1 loop, best of 3: 627 ms per loop
In [7]: %timeit sum(A)
1 loop, best of 3: 1.39 s per loop
¿Cuál podría ser la causa de tal diferencia?
python
arrays
performance
boxing
python-internals
Valentin Lorentz
fuente
fuente
array
paquete. Si quieres hacer cantidades significativas de matemáticas, Numpy opera a la velocidad de la luz (es decir, C), y generalmente es mejor que implementaciones ingenuas de cosas comosum()
).array
es bastante rápido convertir una cadena de enteros (que representan bytes ASCII) en unstr
objeto. El propio Guido solo se le ocurrió esto después de muchas otras soluciones y quedó bastante sorprendido por el rendimiento. De todos modos, este es el único lugar donde recuerdo haberlo visto útil.numpy
es mucho mejor para manejar matrices, pero es una dependencia de terceros.Respuestas:
El almacenamiento está "sin caja", pero cada vez que accede a un elemento, Python tiene que "encajonarlo" (incrustarlo en un objeto Python normal) para poder hacer algo con él. Por ejemplo, su
sum(A)
iteración sobre la matriz y encuadra cada número entero, uno a la vez, en unint
objeto Python normal . Eso cuesta tiempo. En susum(L)
, todo el boxeo se realizó en el momento en que se creó la lista.Entonces, al final, una matriz es generalmente más lenta, pero requiere sustancialmente menos memoria.
Aquí está el código relevante de una versión reciente de Python 3, pero las mismas ideas básicas se aplican a todas las implementaciones de CPython desde que se lanzó Python por primera vez.
Aquí está el código para acceder a un elemento de la lista:
Hay muy poco:
somelist[i]
solo devuelve eli
'th objeto en la lista (y todos los objetos de Python en CPython son punteros a una estructura cuyo segmento inicial se ajusta al diseño de astruct PyObject
).Y aquí está la
__getitem__
implementación de unarray
código de tipo conl
:La memoria sin procesar se trata como un vector de
C
long
enteros nativos de la plataforma ; eli
'thC long
se lee; y luegoPyLong_FromLong()
se llama a envolver ("recuadro") al nativoC long
en unlong
objeto Python (que, en Python 3, que elimina la distinción de Python 2 entreint
ylong
, en realidad se muestra como tipoint
).Este boxeo tiene que asignar nueva memoria para un
int
objeto Python y rociar losC long
bits del nativo en él. En el contexto del ejemplo original, la vida útil de este objeto es muy breve (lo suficiente comosum()
para agregar el contenido a un total acumulado), y luego se necesita más tiempo para desasignar el nuevoint
objeto.Aquí es de donde viene la diferencia de velocidad, siempre ha venido, y siempre vendrá en la implementación de CPython.
fuente
Para agregar a la excelente respuesta de Tim Peters, las matrices implementan el protocolo de buffer , mientras que las listas no. Esto significa que, si está escribiendo una extensión C (o el equivalente moral, como escribir un módulo Cython ), puede acceder y trabajar con los elementos de una matriz mucho más rápido que cualquier cosa que Python pueda hacer. Esto le dará mejoras considerables de velocidad, posiblemente por encima de un orden de magnitud. Sin embargo, tiene una serie de inconvenientes:
Ir directamente a las extensiones C puede estar usando un mazo para aplastar una mosca, dependiendo de su caso de uso. Primero debe investigar NumPy y ver si es lo suficientemente poderoso como para hacer cualquier matemática que intente hacer. También será mucho más rápido que Python nativo, si se usa correctamente.
fuente
Tim Peters respondió por qué esto es lento, pero veamos cómo mejorar .
Siguiendo con su ejemplo de
sum(range(...))
(factor 10 más pequeño que su ejemplo para caber en la memoria aquí):De esta forma, Numpy también necesita box / unbox, que tiene una sobrecarga adicional. Para hacerlo rápido, uno debe permanecer dentro del código c numpy:
Entonces, desde la solución de la lista hasta la versión numpy, este es un factor 16 en tiempo de ejecución.
Veamos también cuánto tiempo lleva crear esas estructuras de datos.
Claro ganador: Numpy
También tenga en cuenta que crear la estructura de datos lleva casi tanto tiempo como sumar, si no más. La asignación de memoria es lenta.
Uso de memoria de aquellos:
Entonces estos toman 8 bytes por número con una sobrecarga variable. Para el rango que utilizamos, los bits de 32 bits son suficientes, por lo que podemos proteger algo de memoria.
Pero resulta que agregar entradas de 64 bits es más rápido que las entradas de 32 bits en mi máquina, por lo que solo vale la pena si está limitado por la memoria / ancho de banda.
fuente
tenga en cuenta que
100000000
es igual a10^8
no hacerlo10^7
, y mis resultados son los siguientes:fuente