У меня дорогой 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);
}
Поскольку класс не изменяется внутри функции, я думаю, что должен быть способ избежать издержек (и ветвления) полиморфизма внутри цикла и выполнить не замужем динамическое приведение и единственный вызов функции без необходимости писать цикл несколько раз.
Что я учел:
proceed
вызов. Я не думаю, что кто-либо из них решает проблему. Это просто разные способы сделать то же самое.
Я подумываю переосмыслить свой дизайн, но до этого я был бы рад услышать любые идеи и предложения, которые могут вам понадобиться для улучшения этого куска кода.
Прежде всего, я бы предпочел сделать то, что предлагает Болдрик в комментарии к ФП, а затем я бы попробовал другие альтернативы, упомянутые в ФП. Я бы каждый раз профилировал / измерял результаты, чтобы принять обоснованное решение.
Если вы еще не удовлетворены, то я предлагаю что-то вроде этого:
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);
}
Иногда это помогает оптимизировать, если вы знаете свои данные.
Если вы знаете, что ваши данные в основном содержат тип A, убедитесь, что это будет первое утверждение в вашем списке if вашей итерации. Я не уверен, хотя, возможно ли это с динамическими объектами.
Может быть, вы можете исключить некоторые вызовы для итерации с помощью оператора if-then-else внутри итерации? Действительно ли необходимо всегда выполнять оба приведения динамических указателей (a_ptr, b_ptr), или могут быть исключения, снижающие рабочую нагрузку?
Вам это поможет?
Вы можете перечислить ваши типы производного класса и, таким образом, избежать двойной отправки — реализовать некоторые 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);
}