Самый быстрый способ упаковать список с плавающей точкой в ​​байты в Python - PullRequest
23 голосов
/ 30 марта 2012

У меня есть список, скажем, 100k float, и я хочу преобразовать его в буфер байтов.

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

Это довольно медленно.Как я могу сделать это быстрее, используя только стандартные библиотеки Python 3.x.

Ответы [ 9 ]

45 голосов
/ 30 марта 2012

Просто скажите struct, сколько у вас float.100 000 поплавков на моем медленном ноутбуке занимает примерно 1/100 секунды.

import random
import struct

floatlist = [random.random() for _ in range(10**5)]
buf = struct.pack('%sf' % len(floatlist), *floatlist)
7 голосов
/ 30 марта 2012

Вы можете использовать ctypes и иметь двойной массив (или массив с плавающей точкой) точно так же, как в C, вместо того, чтобы хранить свои данные в списке. Это довольно низкий уровень, но рекомендуется, если вам нужна высокая производительность и ваш список имеет фиксированный размер.

Вы можете создать эквивалент C double array[100]; в Python, выполнив:

array = (ctypes.c_double * 100)()

Выражение ctypes.c_double * 100 дает класс Python для массива значений типа double, длиной 100 элементов. Чтобы подключить его к файлу, вы можете просто использовать buffer, чтобы получить его содержимое:

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

Если ваши данные уже есть в списке Python, их упаковка в двойной массив может быть, а может и не быть быстрее, чем вызов struct, как в принятом ответе Agf - я оставлю измерение, которое быстрее домашней работы, но весь код вам нужно это:

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

Чтобы увидеть его как строку, просто сделайте: str(buffer(array)) - единственный недостаток в том, что вам нужно позаботиться о размере с плавающей точкой (float vs double) и зависимом от процессора типе float - модуль struct может позаботиться об этом для вас.

Большой выигрыш в том, что с массивом с плавающей запятой вы все еще можете использовать элементы в виде чисел, получая доступ к ним так же, как если бы он находился в простом списке Python, и в то же время был легко доступен в виде плоской области памяти с buffer.

2 голосов
/ 15 декабря 2015

Для массива с плавающей запятой одинарной точности есть два варианта: использовать struct или 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

Так что struct быстрее.

2 голосов
/ 30 марта 2012

Это должно работать:

return struct.pack('f' * len(floatList), *floatList)
1 голос
/ 30 марта 2012

Как и в случае со строками, использование .join() будет быстрее, чем непрерывная конкатенация. Например:

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

Результаты:

b'\xcd\xcc\xac@\x00\x00`@\x9a\x99\xe9@\x9a\x99\xd9@33\x93@'
0 голосов
/ 09 марта 2019

Несколько ответов предлагают

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

, но использование '*' без необходимости преобразует floatlist в кортеж, прежде чем передать его в struct.pack. Этого можно избежать с помощью следующего, который сначала создает пустой буфер, а затем заполняет его, используя самый быстрый способ, который я нашел: назначение слайса:

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

Другая экономия производительности, которую могут использовать некоторые люди:

  • Вы можете повторно использовать существующий буфер, просто выполнив повторное назначение без необходимости создания нового буфера.
  • Вы можете изменить части существующего буфера, назначив соответствующий срез.
0 голосов
/ 25 сентября 2018

На мой взгляд, лучший способ - создать цикл:

например.

import struct 
file_i="test.txt"
fd_out= open ("test_bin_file",'wb')
b = bytes()
f_i = open(file_i, 'r')
for riga in file(file_i):
     line = riga
     print i,float(line)
     i+=1
     b=struct.pack('f',float(line))
     fd_out.write(b)
     fd_out.flush()


fd_out.close()

Чтобы добавить к существующему файлу, используйте:

fd_out= open ("test_bin_file",'ab')
0 голосов
/ 31 марта 2012

Поскольку вы говорите, что вам действительно нужны плавающие числа f с одинарной точностью, вы можете попробовать модуль массива (в стандартной библиотеке с 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'

Это может сэкономить кучу памяти, при этом сохраняя контейнер переменной длины с большинством методов списка. Подход array.array занимает 4 байта на число с плавающей запятой одинарной точности. Подход списка использует указатель на объект с плавающей точкой Python (4 или 8 байт) плюс размер этого объекта; в 32-битной реализации CPython, то есть 16:

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

Итого: 20 байтов на элемент в лучшем случае для list, 4 байта на элемент всегда для array.array('f').

0 голосов
/ 30 марта 2012

Большая часть медлительности будет в том, что вы неоднократно добавляете к байтовой строке.Это копирует строку байта каждый раз.Вместо этого вы должны использовать b''.join():

import struct
packed = [struct.pack('f', val) for val in floatList]
return b''.join(packed)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...