Как кортеж реализован в CPython?

Я пытался узнать, как CPython реализуется под сценой. Замечательно, что Python на высоком уровне, но я не люблю рассматривать его как черный ящик.

Имея это в виду, как реализуются кортежи? Я посмотрел на источник (tupleobject.c), но это происходит над моей головой.

Я вижу, что PyTuple_MAXSAVESIZE = 20 и PyTuple_MAXFREELIST = 2000, что такое сохранение и "бесплатный список"? (Будет ли разница в производительности между кортежами длиной 20/21 или 2000/2001? Что обеспечивает максимальную длину кортежа?)

Ответ 1

Как предостережение, все в этом ответе основано на том, что я почерпнул от просмотра вашей реализации.

Кажется, что стандартная реализация кортежа - это просто массив. Тем не менее, есть множество оптимизаций, чтобы ускорить процесс.

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

Далее, чтобы избежать выделения кучи небольших объектов, CPython перерабатывает память для многих небольших списков. Существует постоянная константа (PyTuple_MAXSAVESIZE), так что все кортежи, меньшие этой длины, имеют право на исправление своего пространства. Всякий раз, когда объект длины, меньший, чем эта константа, освобождается, существует вероятность того, что связанная с ним память не будет освобождена и вместо этого будет сохранена в "свободном списке" (подробнее об этом в следующем абзаце) в зависимости от его размера, Таким образом, если вам когда-либо понадобится выделить кортеж размера n, и ранее он был выделен и больше не используется, CPython может просто переработать старый массив.

Сам бесплатный список реализуется как массив указателей хранения PyTuple_MAXSAVESIZE для неиспользуемых кортежей, где n-й элемент массива указывает либо на NULL (если нет дополнительных наборов размера n), либо на исправленный кортеж размера n. Если существует несколько разных кортежей размера n, которые могут быть повторно использованы, они соединяются вместе в виде связанного списка, имея каждую нулевую точку ввода кортежа в следующий кортеж, который можно использовать повторно. (Так как существует только один кортеж нулевой длины, когда-либо выделенный, никогда не возникает риска прочтения несуществующего нулевого элемента). Таким образом, распределитель может хранить некоторое количество кортежей каждого размера для повторного использования. Чтобы гарантировать, что это не использует слишком много памяти, существует вторая константа PyTuple_MAXFREELIST), которая контролирует максимальную длину любого из этих связанных списков в любом ведре. Затем существует вторичный массив длиной PyTuple_MAXSAVESIZE, который хранит длину связанных списков для кортежей каждой заданной длины, чтобы этот верхний предел не превышался.

В целом, это очень умная реализация!

Надеюсь, это поможет!

Ответ 2

Поскольку в ходе обычных операций Python будет создавать и уничтожать множество небольших кортежей, Python сохраняет для этого внутренний кеш небольших кортежей. Это помогает сократить объем выделения памяти и отторжения памяти. По тем же причинам мелкие целые числа от -5 до 255 интернированы (сделаны в одиночные числа).

Определение PyTuple_MAXSAVESIZE определяет максимальный размер кортежей, подходящих для этой оптимизации, а определение PyTuple_MAXFREELIST определяет, сколько из этих кортежей хранится в памяти. Когда кортеж длины < PyTuple_MAXSAVESIZE отбрасывается, он добавляется в свободный список, если есть еще место для одного (в tupledealloc), которое должно быть повторно использовано, когда Python создает новый небольшой набор (в PyTuple_New).

Python немного разбирается в том, как он хранит их; для каждого кортежа длиной > 0 он будет повторно использовать первый элемент каждого кэшированного кортежа, чтобы связать цепочку PyTuple_MAXFREELIST вместе в связанный список. Поэтому каждый элемент массива free_list является связанным списком объектов кортежа Python, и все кортежи в таком связанном списке имеют одинаковый размер. Единственным исключением является пустой кортеж (длина 0); только один из них необходим, это одноэлемент.

Итак, да, для кортежей по длине PyTuple_MAXSAVESIZE python гарантированно должен выделять память отдельно для новой структуры C, и это может повлиять на производительность, если вы много создадите и отбросите такие кортежи.

Если вы хотите понять внутренности Python C, я рекомендую вам изучить API Python C; это упростит понимание различных структур, которые Python использует для определения объектов, функций и методов в C.

Ответ 3

Спасибо templatetypedef Ответ! Мне было интересно узнать точное значение PyTuple_MAXSAVESIZE и PyTuple_MAXFREELIST, поэтому я использовал google и выяснил:

enter image description here

https://github.com/python/cpython/blob/master/Objects/tupleobject.c

ключевое слово поиска google: PyTuple_MAXSAVESIZE сайт: github.com