Deep Engineering

Python Deep Dive

Как CPython на самом деле исполняет ваш код. Каждая статья написана против закреплённого тега интерпретатора и прямо говорит, что здесь гарантия языка, а что — деталь реализации.

Разделы
5
Статьи
14
ИСТОЧНИКОВ
115
Сложность
14 статей · подходит

Разделы

Статьи

ПродвинутыйОпубликовано3.13Переход на async: потолок ставит пул соединений, а не модель исполненияТысяча корутин при пуле из восьми соединений — это восемь одновременных запросов и очередь из 992. Три модели исполнения на одной и той же работе укладываются в разницу 5 %, а на коротком запросе без одновременности асинхронный код на 16–33 % дороже синхронного.asynciopostgresqlpsycopgasyncpgconnection-pool25 МИН5 ИСТОЧНИКОВЧитать →ПродвинутыйОпубликовано3.12 · 3.13 · 3.14Цикл событий: один поток, очередь и шесть шагов, которые повторяютсяЦикл событий — это обычный while True с шестью шагами внутри. Сколько раз он повторится, задают не задачи, а их приостановки: замените десять корутин на тысячу — повторов не прибавится. Отсюда и главная ловушка: один time.sleep останавливает весь сервис.asyncioevent-loopconcurrencycoroutinescpython-internals30 МИН10 ИСТОЧНИКОВЧитать →ЭкспертныйОпубликовано3.13 · 3.14 · 3.15GIL: не «Python не умеет в потоки», а «поток ждёт столько, сколько вы попросили»Интервал переключения — настройка, которая работает: медиана ожидания следует за ней и на 5 и 50 мс отличается на единицы процентов. Пока не наступает случай, в котором она не работает вовсе — и он измеряется в сотнях миллисекунд.gilthreadingfree-threadingconcurrencycpython-internals35 МИН10 ИСТОЧНИКОВЧитать →ЯдроОпубликовано3.6 · 3.13Словари Python изнутри: хеш-таблица CPythonХеширование, открытая адресация и переписывание словаря в 2016 году, которое сделало порядок вставки побочным эффектом реализации — а через один релиз сделало его гарантией языка.dicthash-tablecpython-internalsdata-structures45 МИН12 ИСТОЧНИКОВЧитать →ПродвинутыйОпубликовано3.12 · 3.13 · 3.14Списки изнутри: рост, сжатие и цена работы с началомУ списка два числа длины, и второго из Python не видно ни одним публичным именем. Из него следует всё остальное: почему одинаковые с виду списки занимают разное место, почему append дёшев, а insert(0) квадратичен, и почему начиная с 3.13 del L[0] и L.pop(0) оставляют объект в разном состоянии — при одинаковой длине в сто элементов список занимает 8056 байт вместо 1528.cpython-internalsdata-structureslistmemoryperformance40 МИН7 ИСТОЧНИКОВЧитать →ПродвинутыйОпубликовано3.12 · 3.13 · 3.14Строки изнутри: три ширины, четыре случая раскладки и цена склейкиУ строки размер объекта при одинаковой длине определяется самым старшим кодом символа в ней: одна кириллическая буква среди латиницы удваивает место под каждый символ, один эмодзи — учетверяет. Отсюда же и два других вопроса: почему `is` для строк иногда срабатывает и почему `s += x` в цикле то линеен, то квадратичен — условий у быстрого пути два, и второго в коде не видно.cpython-internalsdata-structuresstringsunicodememory35 МИН5 ИСТОЧНИКОВЧитать →ЭкспертныйОпубликовано3.11 · 3.12 · 3.13 · 3.14Управление памятью в CPython: три цифры, которые все повторяют неправильноПороги сборщика мусора, размер арены и стоимость объекта — всё это давно не то, что написано в статьях. Каждое число здесь снято с работающего интерпретатора и сверено с исходником на закреплённом теге.memorygcrefcountpymallocfree-threading40 МИН13 ИСТОЧНИКОВЧитать →ПродвинутыйОпубликовано3.11 · 3.12 · 3.13 · 3.14__slots__: что меняет в объектной модели и сколько это стоит в байтахСначала механизм: перечисленные имена получают дескрипторы данных на классе и места по фиксированным смещениям, а __dict__ и __weakref__ перестают создаваться сами. Всё это — контракт языка, одинаковый на 3.11–3.14. И только потом цена, которая контрактом не является: 40 байт на экземпляр, 42 %, причём sys.getsizeof показывает обратное, basicsize молчит про __weakref__, а сумма двух getsizeof отвечает по-разному в начале программы и потом.cpython-internalsmemoryslotsobject-layoutdescriptors40 МИН5 ИСТОЧНИКОВЧитать →ЭкспертныйОпубликовано3.11 · 3.12 · 3.13 · 3.14Адаптивный интерпретатор: как CPython переписывает сам себя по дорогеНа втором выполнении инструкции интерпретатор подменяет её на узкую версию под те типы, которые увидел, — и держит в самом байт-коде счётчик и кеш, чтобы проверять, что предположение ещё в силе. Отсюда и то, почему одна строка, через которую ходят пять типов, дороже на половину, и почему включение JIT в 3.14 проиграло на всех четырёх коротких циклах, на которых его проверили.cpython-internalsspecializationinline-cachejitbytecode20 МИН7 ИСТОЧНИКОВЧитать →ЭкспертныйОпубликовано3.11 · 3.12 · 3.13 · 3.14Что происходит на o.x: дескрипторы, MRO и кеш типаТочка — не чтение поля. Это протокол из четырёх механизмов, и порядок между ними задаётся одной строчкой приоритета. Из неё следует и то, почему присваивание в property без сеттера даёт AttributeError, и почему __getattr__ иногда не вызывается, и почему обращение к унаследованному атрибуту не зависит от глубины наследования — пока цел кеш версий типа. А на 3.14 нашёлся способ выключить этот кеш, ничего про него не зная.cpython-internalsdescriptorsmrotype-cacheattribute-access35 МИН7 ИСТОЧНИКОВЧитать →ЭкспертныйОпубликовано3.12 · 3.13 · 3.14Вызов функции: сколько стоит, что при этом не происходит и где живут ячейкиТри ходовых утверждения о цене вызова — «метод дороже функции», «связать метод заранее дешевле», «staticmethod быстрее» — замер не подтверждает ни одного, а одно опровергает прямо. Причина видна в байт-коде: на быстром пути объект связанного метода не создаётся вовсе. Отсюда же и вторая половина статьи — замыкания: переменная, которую видит вложенная функция, хранится через отдельный объект — ячейку, на которую кадр только ссылается, — и из этого следует всё остальное.cpython-internalsruntimecallsclosuresspecialization40 МИН6 ИСТОЧНИКОВЧитать →ЭкспертныйОпубликовано3.12 · 3.13 · 3.14Цикл интерпретатора: где живёт фрейм и как раздаётся управлениеБайт-код готов — кто его исполняет? Один цикл на C, где фрейм вызова лежит не отдельным объектом в куче, а куском на стеке данных потока, а следующая инструкция выбирается тремя разными способами в зависимости от того, как собран Python. Отсюда и предел рекурсии, и то, что `f_locals` в 3.13 перестал быть снимком.cpython-internalseval-loopframesdispatchbytecode15 МИН8 ИСТОЧНИКОВЧитать →ПродвинутыйОпубликовано3.12 · 3.13 · 3.14От исходника к байткоду: шесть шагов, которые Python делает до первой инструкцииОдна строка кода превращается в четырнадцать токенов, шесть инструкций и шестнадцать ячеек. Десять из шестнадцати не содержат кода вовсе — это место под кеш, которое dis не показывает. Из этой арифметики растёт всё остальное: и специализация, и кеш атрибутов, и указатель под нужным подвыражением в трассировке.bytecodecompilercode-objectastcpython-internals25 МИН11 ИСТОЧНИКОВЧитать →ПродвинутыйОпубликовано3.12 · 3.13 · 3.14Типизация декораторов: почему проверяющий замолкает и чем это лечитсяДекоратор, объявленный через Callable[..., R], пропускает оба заведомо неверных вызова — и это не поломка проверяющего типов, а ровно то, что многоточие означает. При этом в рантайме сигнатура никуда не девается: inspect показывает исходную. Рантайм честен, а проверяющий слеп — и обычно ждут наоборот.typingparamspecconcatenatedecoratorsprotocol25 МИН9 ИСТОЧНИКОВЧитать →

Рекомендуемый порядок

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

  1. 1От исходника к байткоду: шесть шагов, которые Python делает до первой инструкции
  2. 2Цикл интерпретатора: где живёт фрейм и как раздаётся управление
  3. 3Адаптивный интерпретатор: как CPython переписывает сам себя по дороге
  4. 4GIL: не «Python не умеет в потоки», а «поток ждёт столько, сколько вы попросили»
  5. 5Что происходит на o.x: дескрипторы, MRO и кеш типа
  6. 6Вызов функции: сколько стоит, что при этом не происходит и где живут ячейки
  7. 7__slots__: что меняет в объектной модели и сколько это стоит в байтах
  1. 1Словари Python изнутри: хеш-таблица CPython
  2. 2Строки изнутри: три ширины, четыре случая раскладки и цена склейки
  1. 1Цикл событий: один поток, очередь и шесть шагов, которые повторяются
  2. 2Переход на async: потолок ставит пул соединений, а не модель исполнения
  1. 1Управление памятью в CPython: три цифры, которые все повторяют неправильно
  2. 2Списки изнутри: рост, сжатие и цена работы с началом

Можно читать независимо: Типизация декораторов: почему проверяющий замолкает и чем это лечится

Граф знаний

НЕЛИНЕЙНО · С УЧЁТОМ ПРЕДПОСЫЛОК

Темы связаны между собой, и связи здесь не нарисованы, а посчитаны: каждая статья сама называет, на что опирается и с чем соседствует, а картинка собирается из этих названий. Пунктирный кружок — тема, которую статьи уже называют связанной, но которая ещё не написана.

16 · 26 вершин и связей

Связи списком