Выходить из Cpython с плюшевым переводчиком

В Последний пост Я говорил о плюше, языке программирования игрушек с параллелизмом на основе актера, с которым я возился. Реализация все еще незрелая, но она достигла точки, когда я могу написать забавные программы, которые производят 2D/3D графику и параллелизируют вещи по нескольким ядрам ЦП. Что -то, что я хотел бы попробовать в ближайшее время, для удовольствия, это анимация вращающегося куба с помощью программного рендеринга (растизация). Прежде чем я доберусь до этого, я хотел бы немного оптимизировать своего переводчика, чтобы я не оставлял слишком много производительности на столе, что приводит нас к сегодняшней теме.

У моего советника по докторантуре, казалось, была одержимость рекурсивной Фибоначчи Microbenchmark, или просто fib для короткого. Это было почти как ритуал. Каждый раз, когда он поднимал какую -то компилятор или реализацию языка, одной из его первых целей было добраться до того момента, когда он мог бы запустить эту крошечную программу, и если бы вы работали над каким -то компилятором, он спросил бы вас, как быстро он может работать fibПолем Иногда он также просил сравнить производительность вашего компилятора с его компилятором схемы на этом микробенческом, и вы не могли бы сказать, делал ли он какую -то шутку или пытался соревноваться с вами, или оба.

fun fib(n)
{
    if (n < 2)
        return n;

    return fib(n - 1) + fib(n - 2);
}

Хорошо известно, что микробенчки не являются репрезентативными для общей производительности языковых реализаций. Есть много причин для этого, наиболее очевидными в том, что они склонны переоценивать определенные аспекты вашей реализации, полностью игнорируя других. В этом случае fib Microbenchmark с большим подчеркивает, насколько быстро вы можете выполнить функциональные вызовы. Это неплохо измерять, поскольку функциональные вызовы являются центральными для современных языков программирования, и они также могут быть важным источником накладных расходов.

Проверка плюшевого переводчика, я обнаружил, что могу запустить fib(38) За 9,10 секунды со сборкой релиза. Это более 126 миллионов вызовов функций, поэтому около 14 миллионов вызовов функций в секунду. Просто для удовольствия я решил сравнить его с Python 3.13.5 (выпущен в июне 2025 года), и это было в 5,70 -х годах, то есть плюш был примерно на 60% медленнее. Оу. CPYTHON также использует токен-токеновый интерпретатор, поэтому теоретически это не является несправедливым сравнением. Эта ситуация не может быть разрешено стоять, поэтому я решил посмотреть, возможно ли, с несколькими простыми настройками, чтобы добраться до того, что мы могли бы превзойти CPYTHON на fibПолем

Read more:  Рауль Мало из The Mavericks: фронтмен, обладатель премии Грэмми, скончался после отмены дат тура

Я начал с того, что сбрасывал последовательность инструкций ByteCode для fibчто длится 18 инструкций:

# fib
get_arg { idx: 0 }
push { val: Int64(2) }
lt
if_false { target_ofs: 2 }
get_arg { idx: 0 }
ret
get_arg { idx: 0 }
push { val: Int64(1) }
sub
push { val: Fun(FunId(256)) }
call { argc: 1 }
get_arg { idx: 0 }
push { val: Int64(2) }
sub
push { val: Fun(FunId(256)) }
call { argc: 1 }
add
ret

Как и CPYTHON, плюшевый интерпретатор представляет собой виртуальную машину на основе стека. А get_arg инструкция выдвигает значение аргумента функции в стеке, push используется для размещения постоянной на стек и lt выполняет менее чем сравнение и т. Д. Первое, что мне было call инструкция В этом случае мы знаем функцию, которую мы называем, и кажется, что было бы легко объединить push и call в какой -то call_direct Инструкция, которая внедряет функцию, чтобы вызвать инструкцию по байт -коде. Я сделал это изменение, которое сохраняет две инструкции по байт -коде, и это получило меня от 9,10 до 8,44.

Некоторые из вас могут быть задаются вопросом, почему это быстрее. У нас меньше инструкций по байт -коде, но мы действительно делаем те же вычисления, ту же работу, которую мы делали раньше. Важно знать, что большая часть потери производительности, понесенная интерпретатором, происходит от накладных расходов на диспетчерские инструкции. В целом, более крупные инструкции, которые выполняют больше работы, лучше для производительности интерпретатора, потому что они позволяют нам уменьшить накладные расходы. Большие инструкции также дают нашему компилятору большую часть кода для оптимизации в целом, что может привести к более эффективной оптимизации.

Для следующего шага я вытащил Linux perf Инструмент, профилировщик, чтобы лучше взглянуть на основные источники накладных расходов в моем переводчике. Плюш использует систему, в которой она лениво компилирует функции в байт -код. То есть, когда вы запускаете сценарий, все проанализируется в Абстрактное синтаксическое дерево (AST), но функции скомпилируются в Bytecode только при их сначала. Одна из основных причин для этого заключается в том, что на практике до 90% кода в программе никогда не может быть запущено, и поэтому сохраняет время и память, чтобы избежать составления кода, который не выполняется. Другая причина сделать это заключается в том, что чем дольше вы ждете, чтобы скомпилировать функцию, тем больше информации вы должны помочь вам оптимизировать ее.

А call Инструкция, когда вы призваны для функции, просмотрите эту функцию в хэш -таблицу, чтобы увидеть, была ли она уже скомпилирована, и если да, в котором смещение или Программная счетчик (ПК) Он должен прыгнуть. Как я подозревал, этот хэш -поиск добавляет довольно много накладных расходов. Однако я не совсем понял, сколько. Я внедрил новую инструкцию call_pcкоторый может вызвать функцию, которая уже была составлена в Bytecode. Затем я сделал так, чтобы call_direct инструкция при необходимости запустит компиляцию функции, а затем перезаписать себя call_pcкоторый больше не нуждается в поиске хэш. Подобные инструкции по исправлению - это классическая техника оптимизации, которую иногда называют как Самомодирующий кодПолем Удивительно, что предприятие call_pc Убрал нас до 5,13 с. Таким образом, удаление этого хэш -поиска с помощью исправления кода оказало огромное влияние. Это уже значительно быстрее, чем 5,70 -е годы CPYTHON, но можем ли мы добиться большего? Мы, конечно, можем.

Read more:  DPD запускает новую линейку роботов и обещает улучшения

Оглядываясь назад на приведенную выше последовательность Bytecode, есть еще одна очевидная оптимизация, которую мы можем сделать. Программа вычисляет n - 1 и n - 2и для этого он использует 3 инструкции по байт -коде. То, что мы можем легко сделать, это создать новую инструкцию, add_i64чтобы добавить целочисленную постоянную к значению без необходимости выдвигать константу отдельно от add или sub операция В этом случае мы будем добавлять отрицательные целые числа. Очевидно, что мы могли бы играть всевозможные игры с объединением различных инструкций, и в пределах мы могли бы внедрить большую часть эталона в одну инструкцию, которая будет казаться мошенничеством, но добавление и вычитание целых чисел является довольно распространенной операцией, поэтому эта оптимизация должна хорошо обобщать другой код. Эта новая оптимизация уводит нас с 5,13 до 4,67 с.

Я понял, что могу сделать немного больше. Мой add_i64 Реализация сначала поднимает верхнюю часть стека, добавила бы константу, а затем нажмите новое значение сверху стека. Тем не менее, более эффективно изменять верхнюю часть стека на месте, не толкая и не выпуская. Вы могли бы подумать, что компилятор ржавчины будет достаточно умным, чтобы оптимизировать пакет стека и подтолкнуть вместе, но, похоже, это не так. Изменение вершины стека на месте приводит нас к 4,67 с 4,57. Аккуратный! С небольшим количеством сфокусированных усилий, мы сделали плюшевого интерпретатора просто вдвое быстрее на этом микробенческом.

Из любопытства я решил увидеть, насколько эти оптимизации улучшили производительность моей параллельной программы Raytracer и обнаружили ... абсолютно нулевое влияние. Это немного удивительно, учитывая, что Raytracer выполняет множество функций для каждого пикселя. Тем не менее, я начал этот пост, заявив, что микробенчки могут переоценить определенные аспекты реализации, игнорируя других. Проблема здесь в том, что, вероятно, существуют другие (еще хуже) неэффективность, которые влияют на программу Raytracer, но которые не представлены в fibПолем Следите за обновлениями и узнайте, что может быть настолько неэффективным, что это делает эту оптимизацию работать невидимой в следующем эпизоде!

Read more:  Первый коммерческий рейс из Дубая с начала конфликта приземлился в Окленде

Copyright © 2011–2025 Maxime Chevalier-Boisvert. Все права защищены.

2025-08-06 23:18:00


1754527122
#Выходить #из #Cpython #плюшевым #переводчиком

Читайте также

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.