【问题标题】:Is there call overhead if std::bind is used in recursive way?如果 std::bind 以递归方式使用,是否有调用开销?
【发布时间】:2020-12-25 02:17:23
【问题描述】:

我正在编写一个涉及f = std::bind(std::bind(std::bind(...))) 使用的模板函数。

但我不确定 c++ 编译器是否足够聪明,可以展开调用链。
我的意思是:

  1. 创建仿函数f时,是否有多个std::bind() 在运行时调用?
  2. 调用f()时,是否涉及调用 多个operator()在bind_functor对象的不同层。

举个简单的例子:f2() 的运行速度是否比f1() 稍快一些?

#include <functional>

int add(int a, int b) {
  return a + b;
}

int main() {
  using namespace std::placeholders;
  auto f1 = std::bind(std::bind(&add, 1, _1), 2);
  auto f2 = std::bind(&add, 1, 2);
  return 0;
}

更新: 我做了一些实验。看来f2() 确实比f1() 运行得快。如果你使用std::function,它会更慢。这是实验代码(ubuntu/gcc 7.5.0,启用优化。没有优化,f2 是最慢的。)。在我的电脑上,输出是:

f1: 16851813
f2: 17567904
f3: 30655284

代码如下(根据Nate的评论更新):

#include <chrono>
#include <iostream>
#include <functional>

int add(int a, int b, int c) {
  return a + b + c;
}

int main() {
  using namespace std::placeholders;
  auto f1 = std::bind(std::bind(&add, 1, _1, _2), 2, _1);
  auto f2 = std::bind(&add, 1, 2, _1);
  std::function<int(int)> f3 = std::bind(&add, 1, 2, _1);
  const int N = 10000000;
  volatile int x = 0;

  {
    auto begin = std::chrono::system_clock::now();
    for (int n = 0; n < N; n++) {
      x = f1(x);
    }   
    auto end = std::chrono::system_clock::now();
    auto d = end - begin;
    std::cout << "f1: " << d.count() << std::endl;
  }

  x = 0;
  {
    auto begin = std::chrono::system_clock::now();
    for (int n = 0; n < N; n++) {
      x = f2(x);
    }   
    auto end = std::chrono::system_clock::now();
    auto d = end - begin;
    std::cout << "f2: " << d.count() << std::endl;
  }

  x = 0;
  {
    auto begin = std::chrono::system_clock::now();
    for (int n = 0; n < N; n++) {
      x = f3(x);
    }   
    auto end = std::chrono::system_clock::now();
    auto d = end - begin;
    std::cout << "f3: " << d.count() << std::endl;
  }
  return 0;
}

【问题讨论】:

  • 对于 g++ 10.2,您的测试结果可以通过以下事实来解释:gcc 注意到 f1()f2() 没有副作用并且它们的返回值未使用,因此可以优化它们。因此,您的测试正在测量根本不执行任何操作所花费的时间。 godbolt.org/z/TM61b7,请注意,前两个测试中的每一个都只是编译成对now() 的背靠背调用,中间没有循环或其他任何东西。我认为这不是您想要衡量的。
  • 感谢您的评论,内特。我已经更改了代码,以便 f1() f2() 和 f3() 的返回值都将被使用。但我仍然得到了类似的结果,即:就使用的循环时间而言,f2
  • 我尝试使用 godbolt.org。这是一个非常好的工具。我需要更好地了解汇编才能理解编译后的代码。
  • 在使用 -O2 为 x64 编译的 Visual C++ 2019 中,我观察到 f1f2 都内联到一个简单的添加中,而 f3 是对函子实现的调用。差异可以通过编译器中的内联启发式来解释。

标签: c++ functor std-function stdbind


【解决方案1】:

std::function 使用类型擦除,因此应该对operator() 进行等效于虚拟调用的代码,bind 的结果可以知道真实类型并使用静态调用(编译器更容易内联)。

嵌套的bind 会进行额外的静态调用,但可能是内联的,因此可能会给出相同的代码。

Quickbench Demo 确认您的示例:

嵌套或不嵌套bind 的时间相同,但std::function 更慢。

【讨论】:

    猜你喜欢
    • 2015-02-12
    • 1970-01-01
    • 2018-04-09
    • 1970-01-01
    • 2022-11-22
    • 2018-06-18
    • 1970-01-01
    • 2012-03-12
    相关资源
    最近更新 更多