Главная » Хабрахабр » [Перевод] Сколько объектов выделяет Python, выполняя скрипты?

[Перевод] Сколько объектов выделяет Python, выполняя скрипты?

Некоторые Python программисты сильно удивляются, когда узнают сколько временных объектов интерпретатор питона выделяет во время работы простого скрипта.

CPython позволяет получить статистику по выделяемым объектам, для этого его нужно скомпилировать с дополнительными флагами.

./configure CFLAGS='-DCOUNT_ALLOCS' --with-pydebug make -s -j2

После компиляции, мы можем открыть интерактивный REPL и проверить статистику:

>>> import sys
>>> sys.getcounts()
[('iterator', 7, 7, 4), ('functools._lru_cache_wrapper', 1, 0, 1), ('re.Match', 2, 2, 1),
('re.Pattern', 3, 2, 1), ('SubPattern', 10, 10, 8), ('Pattern', 3, 3, 1),
('IndexError', 4, 4, 1), ('Tokenizer', 3, 3, 1), ('odict_keys', 1, 1, 1),
('odict_iterator', 18, 18, 1), ('odict_items', 17, 17, 1), ('RegexFlag', 18, 8, 10),
('operator.itemgetter', 4, 0, 4), ('PyCapsule', 1, 1, 1), ('Repr', 1, 0, 1),
('_NamedIntConstant', 74, 0, 74), ('collections.OrderedDict', 5, 0, 5),
('EnumMeta', 5, 0, 5), ('DynamicClassAttribute', 2, 0, 2), ('_EnumDict', 5, 5, 1),
('TypeError', 1, 1, 1), ('method-wrapper', 365, 365, 2), ('_C', 1, 1, 1),
('symtable entry', 5, 5, 2), ('OSError', 1, 1, 1), ('Completer', 1, 0, 1),
('ExtensionFileLoader', 2, 0, 2), ('ModuleNotFoundError', 2, 2, 1),
('_Helper', 1, 0, 1), ('_Printer', 3, 0, 3), ('Quitter', 2, 0, 2),
('enumerate', 5, 5, 1), ('_io.IncrementalNewlineDecoder', 1, 1, 1),
('map', 25, 25, 1), ('_Environ', 2, 0, 2), ('async_generator', 2, 1, 1),
('coroutine', 2, 2, 1), ('zip', 1, 1, 1), ('longrange_iterator', 1, 1, 1),
('range_iterator', 7, 7, 1), ('range', 14, 14, 2), ('list_reverseiterator', 2, 2, 1),
('dict_valueiterator', 1, 1, 1), ('dict_values', 2, 2, 1), ('dict_keyiterator', 25, 25, 1),
('dict_keys', 5, 5, 1), ('bytearray_iterator', 1, 1, 1), ('bytearray', 4, 4, 1),
('bytes_iterator', 2, 2, 1), ('IncrementalEncoder', 2, 0, 2), ('_io.BufferedWriter', 2, 0, 2),
('IncrementalDecoder', 2, 1, 2), ('_io.TextIOWrapper', 4, 1, 4), ('_io.BufferedReader', 2, 1, 2),
('_abc_data', 39, 0, 39), ('mappingproxy', 199, 199, 1), ('ABCMeta', 39, 0, 39),
('CodecInfo', 1, 0, 1), ('str_iterator', 7, 7, 1), ('memoryview', 60, 60, 2),
('managedbuffer', 31, 31, 1), ('slice', 589, 589, 1), ('_io.FileIO', 33, 30, 5),
('SourceFileLoader', 29, 0, 29), ('set', 166, 101, 80), ('StopIteration', 33, 33, 1),
('FileFinder', 11, 0, 11), ('os.stat_result', 145, 145, 1), ('ImportError', 2, 2, 1),
('FileNotFoundError', 10, 10, 1), ('ZipImportError', 12, 12, 1), ('zipimport.zipimporter', 12, 12, 1),
('NameError', 4, 4, 1), ('set_iterator', 46, 46, 1), ('frozenset', 50, 0, 50), ('_ImportLockContext', 113, 113, 1),
('list_iterator', 305, 305, 5), ('_thread.lock', 92, 92, 10), ('_ModuleLock', 46, 46, 5), ('KeyError', 67, 67, 2),
('_ModuleLockManager', 46, 46, 5), ('generator', 125, 125, 1), ('_installed_safely', 52, 52, 5),
('method', 1095, 1093, 14), ('ModuleSpec', 58, 4, 54), ('AttributeError', 22, 22, 1),
('traceback', 154, 154, 3), ('dict_itemiterator', 45, 45, 1), ('dict_items', 46, 46, 1),
('object', 8, 1, 7), ('tuple_iterator', 631, 631, 3), ('cell', 71, 31, 42),
('classmethod', 58, 0, 58), ('property', 18, 2, 16), ('super', 360, 360, 1),
('type', 78, 3, 75), ('function', 1705, 785, 922), ('frame', 5442, 5440, 36),
('code', 1280, 276, 1063), ('bytes', 2999, 965, 2154), ('Token.MISSING', 1, 0, 1),
('stderrprinter', 1, 1, 1), ('MemoryError', 16, 16, 16), ('sys.thread_info', 1, 0, 1),
('sys.flags', 2, 0, 2), ('types.SimpleNamespace', 1, 0, 1), ('sys.version_info', 1, 0, 1),
('sys.hash_info', 1, 0, 1), ('sys.int_info', 1, 0, 1), ('float', 584, 569, 20),
('sys.float_info', 1, 0, 1), ('module', 56, 0, 56), ('staticmethod', 16, 0, 16),
('weakref', 505, 82, 426), ('int', 3540, 2775, 766), ('member_descriptor', 246, 10, 239),
('list', 992, 919, 85), ('getset_descriptor', 240, 4, 240), ('classmethod_descriptor', 12, 0, 12),
('method_descriptor', 678, 0, 678), ('builtin_function_or_method', 1796, 1151, 651), ('wrapper_descriptor', 1031, 5, 1026),
('str', 16156, 9272, 6950), ('dict', 1696, 900, 810), ('tuple', 10367, 6110, 4337)]

Сделаем вывод более читабельным:

def print_allocations(top_k=None): allocs = sys.getcounts() if top_k: allocs = sorted(allocs, key=lambda tup: tup[1], reverse=True)[0:top_k] for obj in allocs: alive = obj[1]-obj[2] print("Type , allocs: {}, deallocs: {}, max: {}, alive: {}".format(*obj,alive))

>>> print_allocations(10)
Type str, allocs: 17328, deallocs: 10312, max: 7016, alive: 7016
Type tuple, allocs: 10550, deallocs: 6161, max: 4389, alive: 4389
Type frame, allocs: 5445, deallocs: 5442, max: 36, alive: 3
Type int, allocs: 3988, deallocs: 3175, max: 813, alive: 813
Type bytes, allocs: 3031, deallocs: 1044, max: 2154, alive: 1987
Type builtin_function_or_method, allocs: 1809, deallocs: 1164, max: 651, alive: 645
Type dict, allocs: 1726, deallocs: 930, max: 815, alive: 796
Type function, allocs: 1706, deallocs: 811, max: 922, alive: 895
Type code, allocs: 1284, deallocs: 304, max: 1063, alive: 980
Type method, allocs: 1095, deallocs: 1093, max: 14, alive: 2

Где:

  • allocs — сколько объектов было выделено с момента старта интерпретатора
  • deallocs — сколько объектов было удалено (вручную или автоматически)
  • alive — количество живых (текущих) объектов (allocs — deallocs)
  • max — максимальное количество живых объектов с момента старта интерпретатора

Как вы можете видеть, пустой Python REPL успел выделить 17 328 строк and 10 550 кортежей. Это какое-то безумное количество объектов! Здесь нужно иметь в виду, что для работы REPL, Python автоматически импортирует дополнительные модули, которые не импортируются в случае с пустыми скриптами.

Теперь давайте протестируем «Hello, World» на flask:

import sys from flask import Flask
app = Flask(__name__) @app.route('/')
def hello_world(): print_allocations(15) return 'Hello, World!'

./python -m flask run
ab -n 100 http://127.0.0.1:5000/

После отправки 100 HTTP запросов на наш сервер статистика выглядит так:

Type str, allocs: 192649, deallocs: 138892, max: 54320, alive: 53757
Type frame, allocs: 191752, deallocs: 191714, max: 158, alive: 38
Type tuple, allocs: 183474, deallocs: 150069, max: 33581, alive: 33405
Type int, allocs: 85154, deallocs: 81100, max: 4115, alive: 4054
Type bytes, allocs: 31671, deallocs: 14331, max: 17381, alive: 17340
Type list, allocs: 29846, deallocs: 27541, max: 2415, alive: 2305
Type builtin_function_or_method, allocs: 28525, deallocs: 27572, max: 957, alive: 953
Type dict, allocs: 19900, deallocs: 14800, max: 5280, alive: 5100
Type method, allocs: 15170, deallocs: 15105, max: 74, alive: 65
Type function, allocs: 14761, deallocs: 7086, max: 7711, alive: 7675
Type slice, allocs: 12521, deallocs: 12521, max: 1, alive: 0
Type list_iterator, allocs: 10795, deallocs: 10795, max: 35, alive: 0
Type code, allocs: 9849, deallocs: 1749, max: 8107, alive: 8100
Type tuple_iterator, allocs: 8938, deallocs: 8938, max: 4, alive: 0
Type float, allocs: 6033, deallocs: 5889, max: 152, alive: 144

Как можно видеть, flask выделил 847 261 объектов с момента старта интерпретатора. Большая часть из них была временной (714 336) и удалена как только они больше были не нужны. Остальные объекты (132 925) по прежнему находятся в памяти.

Фреймы и code объекты

В примере выше можно встретить множество frame и code объектов. Зачем они нужны?

В Python, самый популярный блок — функция. Если коротко, то каждый code объект хранит в себе блок из скомпилированного кода, в свою очередь frame объекты используются для их выполнения, работая по принципу стэка вызовов. Помимо локальных переменных, каждый frame объект хранит множество вспомогательных данных, которые нужны для выполнения функции. Для каждой новой функции нужен свой code объект, а для каждого вызова этой функции нужен отдельный frame объект, где Python будет хранить локальные переменные.

Откуда берутся все эти объекты?

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

Эти объекты будут жить до конца работы скрипта. Для примера, объявление простой функции создает по меньшей мере 5 словарей, 5 кортежей и 4 списка. Описание среднестатистического класса может выделить сотни контейнерных (словарей, кортежей, списков) объектов. В свою очередь, все эти объекты хранят в себе другие объекты (их элементы), это десятки, иногда сотни дополнительных объектов, используеммых для внутреннего описания скомпилированной функции. К сожалению, здесь уже не получится автоматически подсчитать точное количество выделяемых объектов и эти цифры являются примерными.

Для того, чтобы Python быстро выделял большое количество объектов, в нём используется большая и многослойная система, которая оптимизирует выделение объектов в памяти.

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

S.: Я являюсь автором этой статьи, можете задавать любые вопросы. P.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Конференция «Контентинг» — теперь с поддержкой hyper-threading

Друзья, на связи контент-студия Хабра. 29 ноября мы проводим собственную конференцию про контент и авторов. Будем учить, вдохновлять и рассказывать, как лучше, — в два синхронных потока докладов и дискуссий. Спикеры — эксперты в области контент-маркетинга, которые годами нарабатывали и тестировали ...

J2CL — Лучше поздно, чем никогда

Ещё никому не удалось опоздать на свои похороны.Валентин Домиль Идея трансляции Java в JavaScript далеко не нова, и все уже давно набили шишек с Google Web Toolkit, однако этот продукт сообщество ждало как ни один другой — о нем говорили ...