【问题标题】:Virtual dispatch in a loop循环中的虚拟调度
【发布时间】:2020-11-24 21:04:46
【问题描述】:

我想知道是否有一种方法可以优化以下情况:

struct A{
   virtual process(int x) const = 0;
   virtual ~A() = default;
};

void useA(A const &a){
   for(int i = 0; i < 1000; ++i)
      a.process(i);
}

在这种情况下,如果我们以某种方式承诺不会更改a vtable,则虚拟查找只能在循环之外进行一次。

有没有办法做这样的事情或结果将非常微不足道?或者可能还有其他一些我还没有看到的问题?

【问题讨论】:

  • 如果我没记错的话,一些编译器上有一个命令行标志告诉它你不要在 this 指针上重建另一个对象
  • 我认为你可以使用这个编译选项:clang.llvm.org/docs/…
  • 难道分支(/jump) 预测器不能做正确的工作吗?
  • 如果进程需要“长”时间,虚拟呼叫应该可以忽略不计。如果流程很短,真的有问题吗?如果是的话,也许改变原型直接取范围。
  • 无论您使用什么调度,如果您在数据块上构建与性能相关的调度,而不是单独处理单个数据点,它总是会更快。

标签: c++ optimization vtable


【解决方案1】:

vtable 在循环中的每次迭代都被检查的全部原因与语言中的一个丑陋的漏洞有关,您可以在其中破坏调用虚方法的对象并在同一内存中重新创建另一个对象。你可以用这种方式打一些乒乓球,并在每次调用虚函数时在类之间交换。

我希望我们都同意这样的代码是不可接受的,我们应该能够安全地假设这不会发生在合理的代码库中。如果是这样,您可以通过在 clang 的命令行上传递 -fstrict-vtable-pointers 来告诉编译器。我还没有发现 GCC/MSVC 是否支持相同的标志。

关于我首先提到的代码的有效性,我发现一些 C++ 专家谈论这个仍然感到困惑,请参阅twitter conversion

【讨论】:

  • 快速基准测试无论有无 -fstrict-vtable-pointers 标志都不会显示任何差异:godbolt.org/z/YKfx9d
  • 我应该更详细地研究一下,我需要一个更大的屏幕,这是为了以后
  • 查看您的代码,我什至看不到启用/禁用此标志的程序集有什么不同。你可以在这里看到不同之处:godbolt.org/z/GqaPsr
  • 此外,如果您首先对 a 进行调用,它会做一些不同的事情。 godbolt.org/z/qj94sv 好像循环至少需要执行一次很重要。 if n &gt; 0 不会引起同样的变化。我目前怀疑是否要删除此答案
【解决方案2】:

好问题!

首先,您可以使用多态性以仿函数、std::function 甚至 lambda 的形式返回函数,而不是直接调用虚方法,例如

struct A{
   virtual std::function<...signature...> getProcessMethod() = 0;
   virtual ~A() = default;
};

请参阅以下资源: Why use functors over functions?

https://www.go4expert.com/articles/cpp-closures-functors-lamdas-stdfunction-t34654/


一旦你有了你的函数,你就可以将 functional 技术应用于你正在循环的任何范围/迭代器/向量。因此,您可以使用 std::transform 使用该函数对整个集合执行 map-reduce。这可以优化为使用线程池等。 How to replicate map, filter and reduce behaviors in C++ using STL?


如需更多信息,我衷心推荐 Ivan Cukic 的关于 C++ 函数式编程的书 https://www.amazon.co.uk/Functional-Programming-C-Ivan-Cukic/dp/1617293814

【讨论】:

  • 由于函数是virtual,返回类型应该有一些类型擦除(如std::function),导致代码类似于虚拟调用。
  • std::function 与虚函数调用相比会有大量开销,try it
  • @BeyelerStudios: quick-bench 显示类似的结果。 (但我认为如果可能的话,常规的虚拟通话应该更容易去虚拟化)。
  • @Jarod42 yes clang 似乎很相似,即使 JVApen 提到了 -fstrict-vtable-pointers 标志:godbolt.org/z/YKfx9d 虽然 gcc 和 icc 在虚拟调用上运行得更快。
  • @BeyelerStudios 是的,我怀疑std::function 有很多开销 - 因此我建议将其作为几个选项之一。看到它与仿函数、lambda 和函数指针之间的适当比较会很有趣。
猜你喜欢
  • 2019-09-24
  • 1970-01-01
  • 2012-07-13
  • 2011-09-30
  • 2015-12-15
  • 2015-03-07
  • 2017-02-24
  • 1970-01-01
  • 2022-11-10
相关资源
最近更新 更多