Оптимизация с неизменным полиморфным типом внутри цикла

У меня дорогой for цикл, который занимает больше времени, чем следовало из-за динамических накладных расходов внутри петля.

Пример кода будет следующим (компилируемым)

#include <iostream>
#include <memory>

struct Base {
virtual ~Base() {}
};

struct DerivedA : Base {};
struct DerivedB : Base {};

struct Calculator {
virtual void proceed(const DerivedA& ) const  {
std::cout << "doing A stuff" << std::endl;
}
virtual void proceed(const DerivedB& ) const  {
std::cout << "doing B stuff" << std::endl;
}
};

void doStuff(const std::shared_ptr<Base> &base_ptr) {
Calculator calc;
// Code that does stuff using only Base properties
for(int i = 0; i < 1000000; i++) { // expensive loop
// "redundant" dynamic cast at every iteration
auto a_ptr = std::dynamic_pointer_cast<DerivedA>(base_ptr);
if(a_ptr) calc.proceed(*a_ptr);
auto b_ptr = std::dynamic_pointer_cast<DerivedB>(base_ptr);
if(b_ptr) calc.proceed(*b_ptr);
}
}

int main() {
std::shared_ptr<Base> base_ptr = std::make_shared<DerivedA>();
doStuff(base_ptr);
}

Поскольку класс не изменяется внутри функции, я думаю, что должен быть способ избежать издержек (и ветвления) полиморфизма внутри цикла и выполнить не замужем динамическое приведение и единственный вызов функции без необходимости писать цикл несколько раз.

Что я учел:

  1. В ролях внутри proceed вызов.
  2. Шаблон посетителя.

Я не думаю, что кто-либо из них решает проблему. Это просто разные способы сделать то же самое.

Я подумываю переосмыслить свой дизайн, но до этого я был бы рад услышать любые идеи и предложения, которые могут вам понадобиться для улучшения этого куска кода.

0

Решение

Прежде всего, я бы предпочел сделать то, что предлагает Болдрик в комментарии к ФП, а затем я бы попробовал другие альтернативы, упомянутые в ФП. Я бы каждый раз профилировал / измерял результаты, чтобы принять обоснованное решение.

Если вы еще не удовлетворены, то я предлагаю что-то вроде этого:

template <typename T>
void doStuffImpl(const T &obj) {
Calculator calc;
for(int i = 0; i < 1000000; i++)
calc.proceed(obj);
}

void doStuff(const std::shared_ptr<Base> &base_ptr) {

auto a_ptr = std::dynamic_pointer_cast<DerivedA>(base_ptr);
if (a_ptr)
doStuffImpl(*a_ptr);

auto b_ptr = std::dynamic_pointer_cast<DerivedB>(base_ptr);
if (b_ptr)
doStuffImpl(*b_ptr);
}
2

Другие решения

Иногда это помогает оптимизировать, если вы знаете свои данные.

Если вы знаете, что ваши данные в основном содержат тип A, убедитесь, что это будет первое утверждение в вашем списке if вашей итерации. Я не уверен, хотя, возможно ли это с динамическими объектами.

Может быть, вы можете исключить некоторые вызовы для итерации с помощью оператора if-then-else внутри итерации? Действительно ли необходимо всегда выполнять оба приведения динамических указателей (a_ptr, b_ptr), или могут быть исключения, снижающие рабочую нагрузку?

Вам это поможет?

0

Вы можете перечислить ваши типы производного класса и, таким образом, избежать двойной отправки — реализовать некоторые GetClass() как чистый виртуал на Base

void doStuff(const std::shared_ptr<Base> &base_ptr) {
Calculator calc;
// Code that does stuff using only Base properties
for(int i = 0; i < 1000000; i++) { // expensive loop

switch(base_ptr->GetClass())
{
case TYPE_A:
// "redundant" dynamic cast at every iteration
auto a_ptr = std::dynamic_pointer_cast<DerivedA>(base_ptr);
if(a_ptr)
calc.proceed(*a_ptr);
break;

case TYPE_B:
auto b_ptr = std::dynamic_pointer_cast<DerivedB>(base_ptr);
if(b_ptr)
calc.proceed(*b_ptr);
break;
}
}

int main() {
std::shared_ptr<Base> base_ptr = std::make_shared<DerivedA>();
doStuff(base_ptr);
}
0
По вопросам рекламы [email protected]