Девиртуализация вызовов. Ч2
#опытным
В предыдущем посте мы столкнулись с невозможностью девиртуализировать функцию
Получается, что нам достаточно ограничить внешнее связывание? Рассмотрим в примерах дальше 😊
Запрет на внешнее связывание 1
Итак, мы ведь знаем, что для конкретной функции можно запретить внешнее связывание, например, с помощью
Вызов функции
Кстати, П.2 может быть доказан лишь частично! Например,
В данном случае, с учетом всех наборов аргументов при вызове
Запрет на внешнее связывание 2
В предыдущих способах можно заметить, что сложности возникают с доказательством П.2 и П.4. Компилятор опасается, что в других единицах трансляции появятся либо новые перегрузки, либо будут вызваны функции с объектами других наследников полиморфных классов.
Учитывая особенности сборки проекта, разработчик может намеренно сообщить компилятору, что других единиц трансляции не будет. В частности, для LLVM Clang можно применить следующие опции:
В GCC можно вообще указать, что компилируемая единица и есть вся программа с помощью флага:
Он буквально разрешает считать, что компилятор знает ВСЕ известные перегрузки и их вызовы. Короче, отметит все функции ключевым словом
Запрет на внешнее связывание 3
Еще один способ показать компилятору, что новых полиморфных перегрузок не появится. Можно использовать unnamed namespace:
Теперь данное семейство полиморфных классов будет скрыто от других единиц трансляции, что доказывает компилятору П.3 и П.4, а так же П.2 по месту требования.
Вот такими несложными действиями можно сократить количество обращений к таблице виртуальных методов и ускорить выполнение вашего приложения 😉
#cppcore #hardcore #howitworks
#опытным
В предыдущем посте мы столкнулись с невозможностью девиртуализировать функцию
bar
, т.к. мы не могли гарантировать отсутствие вызовов из других единиц трансляции.Получается, что нам достаточно ограничить внешнее связывание? Рассмотрим в примерах дальше 😊
Запрет на внешнее связывание 1
Итак, мы ведь знаем, что для конкретной функции можно запретить внешнее связывание, например, с помощью
static
. Из живого примера:// direct call!
static void bar(Base &da, Base &db)
{
// push rbx
// mov rax, [rdi]
// mov rbx, rsi
da.vmethod(); // call DerivedA::vmethod()
// mov rdi, rbx
// pop rbx
db.vmethod(); // jmp DerivedB::vmethod()
}
Вызов функции
bar
- единственный в данной единице трансляции, с конкретными наследниками Base
. Следовательно, мы можем доказать П.2, П.4, П.3 (терминология из первой части).Кстати, П.2 может быть доказан лишь частично! Например,
bar
можно вызывать с разными аргументами, тогда оптимизация будет совершена лишь частично:// indirect + direct call
static void bar(Base &da, Base &db)
{
// push rbx
// mov rax, [rdi]
// mov rbx, rsi
da.vmethod(); // call [[rax]]
// mov rdi, rbx
// pop rbx
db.vmethod(); // jmp DerivedB::vmethod()
}
В данном случае, с учетом всех наборов аргументов при вызове
foo
, только второй vmethod
может быть оптимизирован.Запрет на внешнее связывание 2
В предыдущих способах можно заметить, что сложности возникают с доказательством П.2 и П.4. Компилятор опасается, что в других единицах трансляции появятся либо новые перегрузки, либо будут вызваны функции с объектами других наследников полиморфных классов.
Учитывая особенности сборки проекта, разработчик может намеренно сообщить компилятору, что других единиц трансляции не будет. В частности, для LLVM Clang можно применить следующие опции:
-flto -fwhole-program-vtables -fvisibility=hidden
В GCC можно вообще указать, что компилируемая единица и есть вся программа с помощью флага:
-fwhole-program
Он буквально разрешает считать, что компилятор знает ВСЕ известные перегрузки и их вызовы. Короче, отметит все функции ключевым словом
static
: живой пример.Запрет на внешнее связывание 3
Еще один способ показать компилятору, что новых полиморфных перегрузок не появится. Можно использовать unnamed namespace:
namespace
{
struct Base
{
virtual void vmethod();
};
struct Derived : public Base
{
void vmethod() override;
};
}
Теперь данное семейство полиморфных классов будет скрыто от других единиц трансляции, что доказывает компилятору П.3 и П.4, а так же П.2 по месту требования.
Вот такими несложными действиями можно сократить количество обращений к таблице виртуальных методов и ускорить выполнение вашего приложения 😉
#cppcore #hardcore #howitworks