Легенда о том, что «C++ медленный», часто возникает у тех, кто видит исходный код, написанный на C++, и сравнивает его с «магическим» исполнением JVM. Однако скорость языка не определяется только его синтаксисом. Скорость зависит от того, как этот код компилируется, какие оптимизации применяются и как организована работа с памятью.
Вот краткий разбор того, почему C++ может показаться медленным без должного подхода, и как в примере с проектом jank все исправлено:
- Отсутствие автоматической оптимизации: В отличие от компиляторов JVM, которые годами накапливают сложные техники оптимизации, компиляторы C++ (даже современные) часто требуют явных указаний от разработчика.
- Автоматическое наведение ящиков (Boxing): Если не использовать специальные приемы, любой примитивный тип данных (например,
int) в C++ превращается в объект на куче (например,std::unique_ptr<std::shared_ptr<jank::runtime::object_ref>). Это создает нагрузку на сборщик мусора (GC), как это было в примере с функциямиaddиsubвjank. - Инлайн-функции: Без использования директив (например,
[[gnu::always_inline]]), маленькие функции вызывают накладные расходы на переходы между функциями (branch misprediction). - Использование виртуальных таблиц: По умолчанию в C++ полиморфизм использует виртуальные таблицы, что приводит к дополнительным вызовам (vtable lookups).
Автор проекта применил несколько ключевых оптимизаций, которые превратили медленный код в быстрый:
- Избегание Boxing: Использование
into-objectиtruthyбыло устранено, что ускорило выполнение программы. - Настройка линкера и компилятора: Использование директив
[[gnu::always_inline]]и[[gnu::flatten]]позволило объединить функции в один поток, ускорив обработку арифметических операций. - Tagged Pointers (Затегированные указатели): Использование нижних битов указателей для хранения целых чисел позволило избежать выделений памяти для небольших чисел.
- Оптимизация использования NULL: Вместо вызова функции
jank_nil, которая вызывала дополнительные накладные расходы, было принято решение использовать глобальные переменные с конструкторами, которые инициализируют значение вnullptr.
C++ — один из самых мощных и гибких языков программирования, который позволяет достичь производительности, сопоставимой с JVM, при правильном подходе. В примере с jank автор демонстрирует, что даже с использованием C++, можно достичь скорости, превосходящей Java, если учесть особенности работы с памятью и использовать современные приемы оптимизации.
В будущем, как ожидается, jank сможет конкурировать с JVM еще активнее, особенно учитывая возможность использовать асинхронную модель выполнения, которая может дать преимущество над блокирующей моделью JVM в некоторых задачах.