Компания Facebook опубликовала исходные тексты проекта Cinder, развивающего ответвление от CPython 3.8.5, основной эталонной реализации языка программирования Python. Cinder применяется в рабочей инфраструктуре Facebook для обеспечения функционирования сервиса Instagram и включает оптимизации для повышения производительности.
Код опубликован для обсуждения возможности переноса подготовленных оптимизаций в основной состав CPython и для помощи другим проектам, занимающимся повышением производительности CPython. При этом Facebook не собирается поддерживать Cinder в форме отдельного открытого проекта и код представлен в том виде, в котором используется в инфраструктуре компании, без дополнительного причёсывания кода и документирования.
Cinder также не пытаются продвигать как альтернативу CPython – основной целью разработки является желание улучшить сам CPython.
Код отмечается как достаточно надёжный и проверенный в рабочих окружениях, но в случае выявления проблем их придётся решать самостоятельно, так как Facebook не гарантирует, что будет реагировать на внешние сообщения об ошибках и pull-запросы. При этом Fecebook не исключает конструктивное сотрудничество с сообществом и готов обсудить идеи как сделать Cinder ещё быстрее или как ускорить перенос подготовленных изменений в основной состав CPython.
Основные оптимизации, реализованные в Cinder:
- Inline-кэширование байткода (“shadow bytecode”). Суть метода в выявлении ситуаций выполнения типового опкода, которые можно опитимизировать, и динамической замены подобного опкода на более быстрые специализированные варианты (например, замена часто вызываемых функций).
- Активное вычисление сопрограмм (Eager coroutine evaluation). Для вызовов async-функций, которые сразу обрабатываются (await не приводит к ожиданию и функция достигает оператора return раньше), результат подобных функций напрямую подставляется без создания сопрограммы и без привлечения цикла обработки событий. В применяемом в Facebook коде, в котором активно используется async/await, оптимизация приводит к ускорению примерно на 5%.
- Выборочная JIT-компиляция на уровне отдельных методов и функций (method-at-a-time). Включается через опцию “-X jit” или переменную окружения PYTHONJIT=1 и позволяет ускорить выполнение многих тестов производительности в 1.5-4 раза. Так как JIT-компиляция актуальна только для часто выполняемых функций, нецелесообразно применять её для редко используемых функций, накладные расходы на компиляцию которых могут лишь замедлить выполнение программы.
Через опцию “-X jit-list-file=/path/to/jitlist.txt” или переменную окружения “PYTHONJITLISTFILE=/path/to/jitlist.txt” можно указать файл со списком функций, для которых можно использовать JIT (формат path.to.module:funcname или path.to.module:ClassName.method_name). Список функций для которых следует включить JIT можно определить на основе результатов профилирования. В будущем ожидается поддержка динамической JIT-компиляции на основе внутреннего анализа частоты вызова функций, но с учётом специфики запуска процессов в Instagram, предпочтительней JIT-компиляция на начальном этапе.
JIT вначале преобразует байткод Python в высокоуровневное промежуточное представление (HIR), которое достаточно близко к байткоду Python, но рассчитано на использование регистровой виртуальной машины, вместо стековой, а также использует информацию о типах и дополнительные детали, важные для производительности (например, подсчёт ссылок). HIR затем преобразуется в форму SSA (static single assignment) и проходит стадии оптимизации, учитывающие результаты подсчёта ссылок и данные о потреблении памяти. В итоге генерируется низкоуровневное промежуточное представление (LIR), близкое к языку ассемблер. После ещё одной фазы оптимизаций на основе LIR при помощи библиотеки asmjit генерируются ассемблерные инструкции.
- Режим strict для модулей. Функциональность включает три компонента : Тип StrictModule. Статический анализатор, способный определить, что выполнение модуля не оказывает влияние на код за пределами этого модуля. Загрузчик модулей, определяющий, что модули переведены в режим
strict (в коде указывается “import __strict__”), проверяющий отсутствие пересечений с другими модулями и загружающих strict-модули в sys.modules в виде объекта StrictModule. - Static Python – экспериментальный компилятор байткода, использующий аннотации типов для генерации байткода, специфичного для каждого типа и выполняемого быстрее благодаря применению JIT-компиляции. В некоторых тестах сочетание Static Python и JIT демонстрирует повышение производительно до 7 раз, по сравнению с типовым CPython. Во многих ситуациях результаты оцениваются как близкие к применению компиляторов MyPyC и Cython.