С одной стороны, этот ответ должен быть дополнением к ответу @DavidW, но с другой стороны, он также исследует возможные улучшения.В нем также предлагается использовать оболочку для сериализации, которая сохранит любимые объекты CPoint, но обеспечивает ту же плотную сериализацию, что и структурированные numpy-массивы.
Как уже указывалось, сравнивать не имеет особого смысла.размеры одного сериализованного объекта - это слишком много накладных расходов.Помимо прочего, Python должен сохранить идентификатор класса, который представляет собой полное имя модуля + имя класса.В моем случае я использую ipython с %% cython-magic, он довольно длинный:
>>> print(pickle.dumps(CPoint(1,2)))
b'\x80\x03c_cython_magic_46e1a18d1df9b5ea5ee974991f9aba67\n__pyx_unpickle_CPoint\nq\x00c_cython_magic_46e1a18d1df9b5ea5ee974991f9aba67\nCPoint\nq\x01J\xe9\x1a\x8d\x0cK\x01K\x02\x86q\x02\x87q\x03Rq\x04.'
Длина автоматически создаваемого имени модуля равна c_cython_magic_46e1a18d1df9b5ea5ee974991f9aba67
, и это больно!
Таким образом, в принципе, не зная, как хранятся ваши объекты (список, карта, набор или что-то еще), невозможно дать правильный ответ.
Однако, подобно @DavidW, завещаниеПредположим, что точки хранятся в списке.Когда в списке несколько CPoint
объектов, pickle
достаточно умен, чтобы сохранить заголовок класса только один раз.
Я выбираю немного другую тестовую установку - координаты выбираются случайным образом из диапазона[-2e9,2e9]
, который в основном охватывает весь диапазон int32 (приятно знать, что pickle
достаточно умен, чтобы уменьшить количество необходимых байтов для малых значений, но насколько велико усиление, зависит от распределения точек):
N=10000
x_lst=[random.randint(-2*10**9, 2*10**9) for _ in range(N)]
y_lst=[random.randint(-2*10**9, 2*10**9) for _ in range(N)]
и сравнение списков массивов со структурой Point
s, CPoint
s и int32
:
lst_p = [ Point(x,y) for x,y in zip(x_lst, y_lst)]
lst_cp = [ CPoint(x,y) for x,y in zip(x_lst, y_lst)]
lst_np = np.array(list(zip(x_lst, y_lst)), dtype=[('x',np.int32),('y',np.int32)])
, которые дают следующие результаты:
print("Point", len(pickle.dumps(lst_p,protocol=pickle.HIGHEST_PROTOCOL))/N)
print("CPoint", len(pickle.dumps(lst_cp,protocol=pickle.HIGHEST_PROTOCOL))/N)
print("nparray", len(pickle.dumps(lst_np,protocol=pickle.HIGHEST_PROTOCOL))/N)
Point 16.0071
CPoint 25.0145
nparray 8.0213
Это означает, что nparray требуется только 8 байтов на запись (в отличие от ответов @ DavidW, я смотрю на размер всего объекта, а не на целочисленное значение), что так же хорошо, как и получается.Это связано с тем, что я использую np.int32
, а не int
(обычно это 64 бита) для координат.
Один важный момент: numpy-массивы все же лучше, чем список Point
s, даже если бы они имели только маленькие координаты - в этом случае размер был бы около 12 байтов, как показали эксперименты @ DavidW.
Но можно было бы любить объекты CPoint больше, чем структуры numpy.Итак, какие еще варианты у нас есть?
Легкая возможность - не использовать автоматически созданную функцию травления, а сделать это вручную:
%%cython
cdef class CPoint:
...
def __getstate__(self):
return (self.x, self.y)
def __setstate__(self, state):
self.x, self.y=state
А теперь:
>>> pickle.loads(pickle.dumps(CPoint(1,3)))
Point(x=1, y=3)
>>> print("CPoint", len(pickle.dumps(lst_cp,protocol=pickle.HIGHEST_PROTOCOL))/N)
CPoint 18.011
Все еще на 2 байта хуже, чем Point
, но также на 7 байтов лучше, чем оригинальная версия.Плюсом также является то, что мы получили бы выгоду от меньшего размера для меньших целых чисел - но все равно оставались бы на 2 байта в стороне от Point
-версии.
Другой подход заключается в определении выделенного списка CPoints.-class / wraper:
%% массива импорта cython
класс cdef CPointListWrapper: список cdef lst def init (self, lst): self.lst = lst
def release_list(self):
result=self.lst
self.lst=[]
return result
def __getstate__(self):
output=array.array('i',[0]*(2*len(self.lst)))
for index,obj in enumerate(self.lst):
output[index*2] =obj.x
output[index*2+1]=obj.y
return output
def __setstate__(self, in_array):
self.lst=[]
n=len(in_array)//2
for i in range(n):
self.lst.append(CPoint(in_array[2*i], in_array[2*i+1]))
Это очевидно быстро и грязно, и многое можно улучшить с точки зрения производительности, но я надеюсь, что вы поняли суть!А теперь:
>>> print("CPointListWrapper", len(pickle.dumps(CPointListWrapper(lst_cp),protocol=pickle.HIGHEST_PROTOCOL))/N)
CPoint 8.0149
так же хорошо, как numpy, но прилипает к CPoint-объектам!Он также работает правильно:
>>> pickle.loads(pickle.dumps(CPointListWrapper([CPoint(1,2), CPoint(3,4)]))).release_list()
[Point(x=1, y=2), Point(x=3, y=4)]