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.
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)
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 struct
as 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
.
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:
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.
Cela devrait fonctionner:
return struct.pack('f' * len(floatList), *floatList)
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@'
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')
.