Вопрос или проблема
Может кто-то помочь объяснить, почему здесь разница в результатах?
В частности, использование памяти, выведенное после сериализации/десериализации, резко отличается.
Единственное, что я заметил, это то, что df["data"][0].flags
выводит ‘OWNDATA’ по-разному в первом и втором случаях.
import pandas as pd
import numpy as np
import pickle
df = pd.DataFrame({
"data": [np.random.randint(size=1024, low=0, high=100, dtype=np.int8) for _ in range(1_000_000)]
})
print(df["data"].size, df["data"].dtype, df.memory_usage(index=True, deep=True).sum())
# 1000000 object 1144000132
df2 = pickle.loads(pickle.dumps(df))
print(df2["data"].size, df2["data"].dtype, df2.memory_usage(index=True, deep=True).sum())
# 1000000 object 120000132
print(np.array_equal(df["data"][0], df2["data"][0]))
Ответ или решение
Вопрос, который вы подняли, связан с использованием библиотеки Pandas и объектов NumPy в контексте сериализации и десериализации. Давайте более детально рассмотрим, почему результаты использования памяти могут значительно различаться после этих операций.
Краткий обзор проблемы
Вы создали DataFrame, содержащий 1 миллион объектов типа NumPy. Каждый объект – это массив целых чисел типа int8
. После создания DataFrame вы произвели сериализацию и десериализацию с использованием модуля pickle
. В результате вы заметили, что размер памяти, используемой объектом df2
, которая была восстановлена из df
, отличается от исходного DataFrame df
.
Объяснение различий в использовании памяти
-
Объектные типы и память: Когда вы создаете DataFrame, содержащий объекты NumPy (в вашем случае массивы
np.int8
), Pandas сохраняет ссылки на эти массивы как на "объекты". Это означает, что каждый элемент в столбцеdata
фактически является ссылкой на отдельный объект массива в памяти. Поскольку в вашем DataFrame находятся 1 миллион таких ссылок, в началеdf
размер памяти составляет примерно 1.1 Гб. -
Сериализация и десериализация: При сериализации (с использованием
pickle.dumps
) библиотекаpickle
создает последовательность байтов, которая представляет объекты в Python. Когда происходят операции десериализации (pickle.loads
), создаются новые объекты в памяти. Хотя массивы остаются (по сути) такими же, каждый из них инкапсулируется заново, и порядок хранения может отличаться. Это может привести к изменению флагаOWNDATA
, что указывает на то, что объект теперь владеет данными, а не является ссылкой на уже существующий массив. -
Флаги NumPy: Важно отметить, что флаг
OWNDATA
указывает на то, что массив владеет собственными данными, и это может оказывать влияние на использование памяти. Когда вы проверяете флагиdf["data"][0].flags
, и они отличаются дляdf
иdf2
, это может означать, что при десериализации создаются новые массивы, которые хранят данные отдельно, что приводит к увеличенному расходу памяти. -
Размер объектов и внутренние структуры: В зависимости от версии Pandas и использованных библиотек размер обработки объектов может варьироваться после десериализации. Это может быть вызвано изменениями в реализации управления памятью, оптимизации структуры данных или изменениями в зависимости от установленных зависимостей.
Выводы
Разница в использовании памяти между объектами df
и df2
в результате выполнения pickle
операций может объясняться несколькими факторами, включая управление памятью, флаги объектов NumPy и особенности сериализации. Для точной диагностики стоит обратить внимание на:
- Флаги массивов перед и после сериализации.
- Использование
deep=True
в методеmemory_usage
, чтобы получить полное представление о выделенной памяти. - Версии библиотек Pandas и NumPy, так как они могут влиять на управление памятью и структуру объектов.
При необходимости, для оптимизации использования памяти, вы можете рассмотреть варианты хранения данных, такие как использование массивов NumPy напрямую, без обертывания в DataFrame, если это возможно для вашего случая применения.