Непоследовательное использование памяти Pandas для встроенного numpy

Вопрос или проблема

Может кто-то помочь объяснить, почему здесь разница в результатах?

В частности, использование памяти, выведенное после сериализации/десериализации, резко отличается.

Единственное, что я заметил, это то, что 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.

Объяснение различий в использовании памяти

  1. Объектные типы и память: Когда вы создаете DataFrame, содержащий объекты NumPy (в вашем случае массивы np.int8), Pandas сохраняет ссылки на эти массивы как на "объекты". Это означает, что каждый элемент в столбце data фактически является ссылкой на отдельный объект массива в памяти. Поскольку в вашем DataFrame находятся 1 миллион таких ссылок, в начале df размер памяти составляет примерно 1.1 Гб.

  2. Сериализация и десериализация: При сериализации (с использованием pickle.dumps) библиотека pickle создает последовательность байтов, которая представляет объекты в Python. Когда происходят операции десериализации (pickle.loads), создаются новые объекты в памяти. Хотя массивы остаются (по сути) такими же, каждый из них инкапсулируется заново, и порядок хранения может отличаться. Это может привести к изменению флага OWNDATA, что указывает на то, что объект теперь владеет данными, а не является ссылкой на уже существующий массив.

  3. Флаги NumPy: Важно отметить, что флаг OWNDATA указывает на то, что массив владеет собственными данными, и это может оказывать влияние на использование памяти. Когда вы проверяете флаги df["data"][0].flags, и они отличаются для df и df2, это может означать, что при десериализации создаются новые массивы, которые хранят данные отдельно, что приводит к увеличенному расходу памяти.

  4. Размер объектов и внутренние структуры: В зависимости от версии Pandas и использованных библиотек размер обработки объектов может варьироваться после десериализации. Это может быть вызвано изменениями в реализации управления памятью, оптимизации структуры данных или изменениями в зависимости от установленных зависимостей.

Выводы

Разница в использовании памяти между объектами df и df2 в результате выполнения pickle операций может объясняться несколькими факторами, включая управление памятью, флаги объектов NumPy и особенности сериализации. Для точной диагностики стоит обратить внимание на:

  • Флаги массивов перед и после сериализации.
  • Использование deep=True в методе memory_usage, чтобы получить полное представление о выделенной памяти.
  • Версии библиотек Pandas и NumPy, так как они могут влиять на управление памятью и структуру объектов.

При необходимости, для оптимизации использования памяти, вы можете рассмотреть варианты хранения данных, такие как использование массивов NumPy напрямую, без обертывания в DataFrame, если это возможно для вашего случая применения.

Оцените материал
Добавить комментарий

Капча загружается...