TEC Приёмы Приём

Сколько займёт в памяти

Также: Estimating memory · память программы · sys.getsizeof · tracemalloc · runtime.MemStats · __slots__ · выравнивание полей · padding · struct of arrays

Каталог землетрясений весит в файле полтора мегабайта, а в памяти Python, прочитанный обычным csv.DictReader, — двенадцать. Откуда восьмикратная прибавка, сколько весят число, строка, словарь и структура в Python и в Go, и как прикинуть, поместятся ли данные в память, до того как программа упадёт.

Средний 15 минут оценка замеры массив хеш-таблица в проде

Каталог землетрясений M5+ за одиннадцать лет — 19 073 строки, полтора мегабайта текста. Каждая строка — время, широта, долгота, глубина, магнитуда, тип магнитуды и место, около восьмидесяти байт. Прочитаем его в Python самым обычным способом, list(csv.DictReader(f)), и он займёт в памяти больше двенадцати мегабайт — в восемь раз больше файла. Ниже — тот же каталог, хранимый семью способами в Python и в Go.

Самый тяжёлый способ — словари: больше шестисот байт на землетрясение, из них около двухсот семидесяти — сама хеш-таблица словаря на семь ключей, а остальное — семь отдельных строк. Кортежи и объекты с __slots__ вдвое легче, столбцы — вчетверо. В Go всё в несколько раз компактнее, но и там срез словарей map[string]string весит почти втрое больше среза структур. Подвиньте ползунок: при миллионе записей словари Python — это уже больше полугигабайта, при ста миллионах не хватит и ноутбука.

Идея

Вес данных складывается из двух частей: самих значений и того, во что они завёрнуты. В Go значение — это почти всегда его байты: число — 8 байт, структура — сумма полей с небольшими промежутками. В Python каждое значение — отдельный объект с заголовком: число весит 24–28 байт вместо 8, строка — 41 байт плюс текст. Прикидка сводится к умножению: байты на запись × число записей. Байты на запись можно посчитать по таблице ниже или измерить на тысяче записей.

Обратите внимание на слово «записей». Важно не то, сколько весит файл, а то, на сколько объектов он распадётся в памяти: тысяча длинных строк и миллион коротких чисел весят совсем по-разному, даже если файлы одного размера.

Что сколько весит в Python

На 64-битном CPython 3.13 функция sys.getsizeof показывает:

ОбъектБайтОткуда
целое число (не из маленьких)28заголовок 24 и одна «цифра» по 30 бит
дробное число24заголовок 16 и сам double
строка ASCII из n символов41 + nс кириллицей — 58 + 2n: два байта на символ
кортеж из n40 + 8nплюс сами элементы
список из n56 + 8n и запасплюс сами элементы
словарь на 7 ключей272хеш-таблица с запасом; плюс ключи и значения
объект класса со __slots__ на 7 полей88заголовок 32 с данными сборщика мусора и 8 байт на поле; без __slots__ — объект 48 байт и свой словарь почти на 300
array('d') из n чисел80 + 8nчисла без заголовков, подряд

Числа от −5 до 256 Python хранит в одном экземпляре, и списки из них дешевле: в них лежат только указатели. Одинаковые строки — нет: csv создаёт новую строку на каждое значение. В нашем каталоге у типа магнитуды всего девять разных значений, а строк «mww» и «mb» в памяти девятнадцать тысяч. sys.intern или словарь замен оставит по одной копии каждой и сэкономит больше восьмисот килобайт — четверть столбцового варианта.

Цифры по таблице можно проверить двумя приборами. sys.getsizeof говорит о самом объекте, но не о тех, на кого он ссылается, поэтому вес структуры данных — это рекурсивная сумма по всем объектам, где общие считаются один раз. tracemalloc видит каждое выделение памяти. На всех четырёх способах хранения каталога они сходятся с точностью до сотых долей мегабайта.

Что сколько весит в Go

В Go число int или float64 — 8 байт без всяких заголовков. Строка — заголовок в 16 байт (указатель на текст и длина) плюс сам текст; срез — 24 байта (указатель, длина, ёмкость) плюс массив. Структура — её поля подряд, но не вплотную: каждое поле лежит по адресу, кратному его выравниванию, и между полями бывают пустые байты.

Шесть полей — три bool, float64, int32 и int64 — в порядке «как получилось» занимают 40 байт, из них 17 пустые. Если положить поля от больших к маленьким, получится 24. На миллионе таких записей это 16 мегабайт разницы из-за одного порядка строк в определении. Как устроено выравнивание и почему порядок «от больших к маленьким» оптимален, разбирает задача ниже.

Словарь map[string]string в Go дешевле питоновского, но всё равно тяжёл: на каждую из семи пар — два строковых заголовка по 16 байт, плюс служебная часть таблицы. На замере больше половины веса этого способа — заголовки строк и таблицы, а не текст.

Код

Каталог четырьмя способами в Python, вес по рекурсивной сумме и по tracemalloc, и вес отдельных объектов:

В Go вес меряется по куче: собрать мусор, запомнить размер кучи, построить данные, снова собрать мусор и вычесть. Строки копируются явно — иначе все поля указывали бы в общий кусок памяти, прочитанный из CSV, и мерили бы мы не то. Последние строки — размер структуры при двух порядках полей.

Как прикидывать

  1. Посчитайте записи и поля. Миллион записей по семь полей — семь миллионов значений.
  2. Оцените байты на значение. Python: число — 24–28 байт, строка — 41 плюс длина, контейнер записи — от 40 до 300. Go: число — 8, строка — 16 плюс длина.
  3. Умножьте и добавьте запас. Списки и срезы растут с запасом до четверти и до половины ёмкости; во время построения данные иногда существуют в двух копиях.
  4. Измерьте на тысяче записей и умножьте — это точнее любой таблицы и занимает минуту.

Если прикидка не помещается в память, у вас три пути. Сменить представление: столбцы вместо объектов, числа вместо строк (время — восемь байт секунд вместо строки в 60 байт), один экземпляр вместо тысячи одинаковых. Сменить язык или библиотеку: numpy и pandas хранят столбцы так же, как Go. Или не держать всё сразу: читать поток по частям, как внешняя сортировка. Как раскладка в памяти влияет ещё и на скорость, рассказывает статья «Данные, удобные кэшу».

Задачи

Задача — посчитать размер структуры Go так, как это делает компилятор, и найти порядок полей, при котором она легче всего. Ответы сверены с unsafe.Sizeof в настоящем Go.

Выравнивание полей

Данные: Землетрясения: каталог USGS, общественное достояние