2012-03-30 10 views
17

Tengo una lista de 100k flotantes y quiero convertirla en un búfer de bytes.La forma más rápida de empaquetar una lista de flotantes en bytes en python

buf = bytes() 
for val in floatList: 
    buf += struct.pack('f', val) 
return buf 

Esto es bastante lento. ¿Cómo puedo hacerlo más rápido usando solo las bibliotecas estándar de Python 3.x.

+1

''f'' te da un C * f * loat (32 bits); usted sin duda quiere un flotador de Python también conocido como C * d * ouble (64 bits) para que usted y sus seguidores estén usando '' d'' –

+0

Tiene razón, estaba siendo descuidado con mis tipos de datos. Pero en realidad quería flotadores de precisión simple. – MxyL

Respuesta

37

Solo dígale a struct cuántos float tiene. Flotadores de 100k tarda aproximadamente 1/100th de segundo en mi computadora portátil lenta.

import random 
import struct 

floatlist = [random.random() for _ in range(10**5)] 
buf = struct.pack('%sf' % len(floatlist), *floatlist) 
+1

FWIW: funciona con 'array.array' pero vale la pena señalar que' array.tobytes() 'devuelve lo mismo que' struct.pack (...) '. Entonces uno puede usar un 'array ('f', [...])' y tener 'append', accesibilidad del indexador, etc. Array.array no tiene todos los mismos métodos que' list', pero podría ser más fácil para implementar en muchos casos. – IAbstract

0

La mayor parte de la lentitud será que se agrega repetidamente a una cadena de bytes. Que copia la cadena de bytes cada vez. En su lugar, debe utilizar b''.join():

import struct 
packed = [struct.pack('f', val) for val in floatList] 
return b''.join(packed) 
+3

Esto es aproximadamente seis veces más lento que llamar 'struct' una vez por 100k' float's. – agf

2

Al igual que con cuerdas, utilizando .join() será más rápido que la concatenación de forma continua. Por ejemplo:

import struct 
b = bytes() 
floatList = [5.4, 3.5, 7.3, 6.8, 4.6] 
b = b.join((struct.pack('f', val) for val in floatList)) 

Resultados en:

b'\xcd\xcc\[email protected]\x00\x00`@\x9a\x99\[email protected]\x9a\x99\[email protected]\[email protected]' 
1

Eso debería funcionar:

return struct.pack('f' * len(floatList), *floatList) 
5

Puede utilizar ctypes, y tienen un doble-array (o flotar matriz) exactamente como usted' tener en C, en lugar de mantener sus datos en una lista. Esto es justo nivel bajo, pero es una recomendación si necesita un gran rendimiento y si su lista es de un tamaño fijo.

Puede crear el equivalente de un C double array[100]; en Python haciendo:

array = (ctypes.c_double * 100)() 

La expresión ctypes.c_double * 100 produce una clase de Python para una serie de dobles, 100 objetos largos. Para conectar a un archivo, sólo puede utilizar buffer para obtener su contenido:

>>> f = open("bla.dat", "wb") 
>>> f.write(buffer(array)) 

Si los datos ya está en una lista de Python, el embalaje en una doble matriz puede o no puede ser más rápido que llamar struct como en respuesta aceptada de AGF - dejaré de medición, que es más rápido que la tarea, pero todo el código que necesita es la siguiente:

>>> import ctypes 
>>> array = (ctypes.c_double * len(floatlist))(*floatlist) 

Para verlo como una cadena, acaba de hacer: str(buffer(array)) - el único inconveniente aquí es que se tiene que ocuparse del tamaño del flotador (flotación vs doble) y del tipo de flotación dependiente de la CPU: el módulo struct puede encargarse de esto por usted.

La gran ventaja es que con una matriz flotante puede utilizar los elementos como números, accediendo entonces como si fuera una lista simple de Python, teniendo luego disponible como región de memoria planar con buffer.

0

Como dices que realmente quieres flotadores 'f' de precisión simple, te gustaría probar el array module (en la biblioteca estándar desde 1.x).

>>> mylist = [] 
>>> import array 
>>> myarray = array.array('f') 
>>> for guff in [123.45, -987.654, 1.23e-20]: 
... mylist.append(guff) 
... myarray.append(guff) 
... 
>>> mylist 
[123.45, -987.654, 1.23e-20] 
>>> myarray 
array('f', [123.44999694824219, -987.6539916992188, 1.2299999609665927e-20]) 
>>> import struct 
>>> mylistb = struct.pack(str(len(mylist)) + 'f', *mylist) 
>>> myarrayb = myarray.tobytes() 
>>> myarrayb == mylistb 
True 
>>> myarrayb 
b'f\xe6\xf6B\xdb\xe9v\xc4&Wh\x1e' 

Esto puede ahorrarle una bolsa de memoria, al tiempo que tiene un contenedor de longitud variable con la mayoría de los métodos de la lista. La matrizel enfoque de matriz requiere 4 bytes por flotante de precisión simple. El enfoque de lista consume un puntero a un objeto flotante de Python (4 u 8 bytes) más el tamaño de ese objeto; en una aplicación CPython de 32 bits, es decir 16:

>>> import sys 
>>> sys.getsizeof(123.456) 
16 

total: 20 bytes por mejor de los casos un elemento de list, 4 bytes por elemento siempre para un array.array('f').

0

Para la matriz de flotador de precisión simple hay dos opciones: usar struct o array.

In[103]: import random 
import struct 
from array import array 

floatlist = [random.random() for _ in range(10**5)] 

In[104]: %timeit struct.pack('%sf' % len(floatlist), *floatlist) 
100 loops, best of 3: 2.86 ms per loop 

In[105]: %timeit array('f', floatlist).tostring() 
100 loops, best of 3: 4.11 ms per loop 

So struct es más rápido.

Cuestiones relacionadas