惰性构造 - 虚拟方法与 if-then 存根 getter

Lazy construction - virtual method vs. if-then to stub a getter

我的问题的背景是我试图创建一个惰性网格结构,其中网格区域仅在需要时实例化,否则它们 return 在查询时为默认值。

稍微归结一下问题,考虑以下我的情况模型:

struct Container {
  std::vector<Base> data;

  float get(int indexOuter, int indexInner) {
     return data[indexOuter].get(indexInner);
  }
}

我想在某些情况下将 Base::get 函数存根以始终 return 相同的值,而在其他情况下我想 return 某个数组中的值。我想象两种可能的解决方案。

第一个解决方案是在 Base 上使用标志,即

struct Base {
  std::vector<float> data;

  float get(int indexInner) {
    if (data.empty()) return 0;
    return data[indexInner];
  }
}

此解决方案将涉及在对象进入 "stub mode" 时销毁 data(将大小调整为零),并在对象再次具体化时重新创建 data

我能想到的第二种方案是继承,即

struct Container {
  std::vector<Base*> data;

  float get(int indexOuter, int indexInner) {
     return data[indexOuter]->get(indexInner);
  }
}
struct Base {
  virtual float get(int indexInner) = 0;
}
struct Stub : public Base {
  float get(int indexInner) {
    return 0;
  }   
}
struct Concrete : public Base {
  std::vector<float> data;

  float get(int indexInner) {
    return data[indexInner];
  }   
}

然后在 Containerdata 数组中必要时将 Concrete 的实例替换为 Stub 的实例,反之亦然(注意确保彻底破坏)。

问题是性能问题之一。 Container::get 每秒将被调用 1000 次,甚至更多。但是,Bases entering/exiting "stub mode" 出现的频率要低得多。

对于上面提出的两种解决方案,第一种解决方案涉及一个额外的 if-then 子句,而第二种解决方案涉及从 Base 指针到对象的间接寻址,以及从抽象方法到派生的实现。

这些解决方案中的哪一个可以提供最佳性能?还有其他我没有考虑过的性能更高的解决方案吗?

大多数现代处理器不喜欢条件代码,特别是如果它不是 "predictable"(换句话说,您的数据有时会被填充,有时不会。

通过指向函数的指针跳转通常比条件分支更快(考虑到您还需要检查它是否被存根 [虽然您可以使用 data.empty() 这将很容易检查编译器]).

因此,如果不对这两个解决方案进行基准测试,我的猜测是虚拟 StubConcrete 会更快。但是,这将有点取决于 use-cases.

Stub实现也将占用较少的数据。另一方面,如果这是在 std::vector<Base*> data; 中使用指针的唯一原因,那么您可能要考虑使用 isStubbed(或 data.empty())并使用 std::vector<Base> 相反 - 整体上节省了一定程度的间接寻址 - 这可能会更好,具体取决于具体情况。

最终,如果它对性能很重要,您将希望同时实施 运行 具有不同 load-patterns 的基准测试并测量时间。还要查看每个 运行 的分析数据,以了解代码将时间花在哪里。