Посмотрел недавно видосик про «убийцу Rust» — Гуся. Там заявления, что он быстрее Rust и плюсов, бенчмарки, всё такое. Автор прогнал свои тесты — Гусь у него проиграл, как и ожидалось, можно было даже не проверять 😂
И мне стало интересно: а как это всё будет работать на Питоне?
В первом примере числа генерируются через xorshift, а потом считается их сумма. Написал свой вариант на Питоне, запустил — очень медленно.
Сначала подумал, что это я где-то накосячил. Написал отдельный бенч, погонял разные разрядности. Оказалось, всё я делаю правильно. Питон действительно медленный (хотя чего я ожидал). Я-то думал: бл*дь, ну хотя бы такая числодробилка должна работать побыстрее. Но… нет.
Ладно, пошёл смотреть, на что он вообще тратит время. Прогнал через perf, открыл флеймграф — long_bitwise сжирает больше 60% времени моего бенча. И это на сборке с PGO и LTO, если что.
Полез в исходники. Заглянул в long_bitwise, а там временные аллокации, лишние проходы по разрядам — в общем, есть что убирать. Решил поковырять.
Сделал первый фикс. На остальных бенчах всё стало быстрее, на xorshift для 32 бит — наравне с main. А вот на 64, 128 и 256 бит результаты плавали и местами были чуть хуже. Совсем чуть-чуть, но медленнее — это всё равно медленнее. Так оставлять не хотелось.
Начал разбираться дальше. Обработка положительных и отрицательных чисел сидела в одной функции. Решил разнести их и посмотреть, что получится с инлайнингом.
Во втором фиксе вынес обработку отрицательных чисел в long_bitwise_negative и поставил Py_NO_INLINE, чтобы компилятор её не инлайнил. А long_bitwise, где остался путь для неотрицательных чисел, наоборот, пометил Py_ALWAYS_INLINE. Чтобы быстрый путь можно было нормально заинлайнить, не затаскивая туда заодно всю обработку отрицательных чисел.
И помогло. На xorshift для 32, 64, 128 и 256 бит теперь тоже быстрее: от 1,01× до 1,05×, то есть примерно на 1–5%. Немного, но уже в нужную сторону. На остальных бенчах ускорение сохранилось, регрессий в моих прогонах не осталось.
В итоге открыл ишак в CPython












