在循环中使用无变量多态类型进行优化

Optimization with unvariant polymorphic type inside a loop

本文关键字:类型 多态 优化 变量 循环      更新时间:2023-10-16

我有一个昂贵的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. 访客模式。

我不认为他们中的任何一个能解决问题。这些只是做同一件事的不同方式。

我正在考虑重新考虑我的设计,但在此之前,我很高兴听到您可能需要改进这段代码的任何想法和建议。

首先,我宁愿按照鲍德里克在对 OP 的评论中提出的建议去做,然后我会尝试 OP 中引用的其他替代方案。我每次都会分析/预测结果以做出明智的决定。

如果您还不满意,那么我建议如下:

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),或者是否有减少工作负载的异常?

这对你有帮助吗?

您可以枚举派生类的类型,从而避免双重调度 - 在Base上实现一些GetClass()作为纯虚拟

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);
}