web-dev-qa-db-fra.com

Le moyen le plus rapide de compresser une liste de flottants en octets dans python

J'ai une liste de disons 100k floats et je veux le convertir en un tampon d'octets.

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

C'est assez lent. Comment puis-je le rendre plus rapide en utilisant uniquement les bibliothèques standard Python 3.x.

25
That Umbrella Guy

Dites simplement à struct combien de float vous avez. 100k floats prend environ un 1/100e de seconde sur mon ordinateur portable lent.

import random
import struct

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

Vous pouvez utiliser des ctypes et avoir un double tableau (ou tableau flottant) exactement comme vous le feriez en C, au lieu de conserver vos données dans une liste. C'est un niveau assez bas, mais c'est une recommandation si vous avez besoin de grandes performances et si votre liste est de taille fixe.

Vous pouvez créer l'équivalent d'un C double array[100]; Dans Python en faisant:

array = (ctypes.c_double * 100)()

L'expression ctypes.c_double * 100 Donne une classe Python pour un tableau de doubles, 100 éléments. Pour la câbler à un fichier, vous pouvez simplement utiliser buffer pour obtenir son contenu:

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

Si vos données sont déjà dans une liste Python, les emballer dans un double tableau peut ou non être plus rapide que d'appeler structas dans la réponse acceptée par Agf - je laisserai la mesure qui est plus rapide comme devoir, mais tout le code dont vous avez besoin est le suivant:

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

Pour le voir comme une chaîne, faites simplement: str(buffer(array)) - le seul inconvénient ici est que vous devez faire attention à la taille du flottant (float vs double) et au type de flottant dépendant du CPU - le module struct peut prendre en charge pour vous.

La grande victoire est qu'avec un tableau flottant, vous pouvez toujours utiliser les éléments comme des nombres, en y accédant comme si c'était une simple liste Python, tout en étant facilement disponible en tant que région de mémoire planaire avec buffer.

7
jsbueno

Quelques réponses suggèrent

import struct
buf = struct.pack(f'{len(floatlist)}f', *floatlist)

mais l'utilisation de '* 'convertit inutilement floatlist en Tuple avant de le passer à struct.pack. Cela peut être évité par ce qui suit, qui crée d'abord un tampon vide, puis le remplit à l'aide de la méthode la plus rapide que j'ai trouvée: affectation de tranche:

import ctypes
buf = (ctypes.c_double * len(floatlist))()
buf[:] = floatlist

D'autres économies de performances que certaines personnes pourraient utiliser:

  • Vous pouvez réutiliser un tampon existant en refaisant simplement l'affectation, sans avoir à créer un nouveau tampon.
  • Vous pouvez modifier des parties d'un tampon existant en les affectant à la tranche appropriée.
2
Jonathan Hartley

Pour un tableau de float simple précision, il y a deux options: utiliser struct ou 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

Donc struct est plus rapide.

2
Sklavit

Cela devrait fonctionner:

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

Comme pour les chaînes, l'utilisation de .join() sera plus rapide que la concaténation continue. Par exemple:

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))

Résulte en:

b'\xcd\xcc\xac@\x00\x00`@\x9a\x99\xe9@\x9a\x99\xd9@33\x93@'
1
Gareth Latty

Comme vous dites que vous voulez vraiment des flotteurs "f" simple précision, vous pouvez essayer le module de tablea (dans la bibliothèque standard depuis 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'

Cela peut vous faire économiser beaucoup de mémoire, tout en ayant un conteneur de longueur variable avec la plupart des méthodes de liste. L'approche array.array prend 4 octets par flotteur simple précision. L'approche par liste consomme un pointeur vers un objet flottant Python (4 ou 8 octets) plus la taille de cet objet; sur une implémentation CPython 32 bits, soit 16:

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

Total: 20 octets par élément dans le meilleur des cas pour un list, 4 octets par élément toujours pour un array.array('f').

0
John Machin