【问题标题】:Execution speed of code with `function` object as compared to using template functions与使用模板函数相比,使用 `function` 对象的代码的执行速度
【发布时间】:2022-07-27 03:17:04
【问题描述】:

我知道std::function 是用 type erasure 习惯用法实现的。类型擦除是一种方便的技术,但作为一个缺点,它需要在堆上存储底层对象的寄存器(某种数组)。

因此,在创建或复制 function 对象时需要进行分配,因此该过程应该比简单地将函数作为模板类型进行操作要慢。

为了检查这个假设,我运行了一个测试函数,它累积 n = cycles 连续整数,然后将总和除以增量数 n。 首先编码为模板:

#include <iostream>
#include <functional>
#include <chrono>
using std::cout;
using std::function;
using std::chrono::system_clock;
using std::chrono::duration_cast;
using std::chrono::milliseconds;

double computeMean(const double start, const int cycles) {
    double tmp(start);
    for (int i = 0; i < cycles; ++i) {
        tmp += i;
    }
    return tmp / cycles;
}

template<class T>
double operate(const double a, const int b, T myFunc) {
    return myFunc(a, b);
}

还有main.cpp

int main()
{
    double init(1), result;
    int increments(1E9);
    // start clock
    system_clock::time_point t1 = system_clock::now();

    result = operate(init, increments, computeMean);
    // stop clock
    system_clock::time_point t2 = system_clock::now();

    cout << "Input: " << init << ", " << increments << ", Output: " << result << '\n';
    cout << "Time elapsed: " << duration_cast<milliseconds>(t2 - t1).count() << " ms\n";
    return 0;
}

这运行了一百次,平均结果为10024.9 ms

然后我在main中引入function对象,加上operate的模板特化,这样上面的代码就可以循环使用了:

// as above, just add the template specialization
template<>
double operate(const double a, const int b, function<double (const double, const int)> myFunc) {
    cout << "nontemplate called\n";
    return myFunc(a, b);
}

// and inside the main
int main()
{
    //...
    // start clock
    system_clock::time_point t1 = system_clock::now();

    // new lines
    function<double (const double, const int)> computeMean =
        [](const double init, const int increments) {
            double tmp(init);
            for (int i = 0; i < increments; ++i) {
                tmp += i;
            }
            return tmp / increments;
        };
    // rest as before
    // ...
}

我预计function 版本会更快,但平均速度差不多,实际上甚至更慢,result = 9820.3 ms。 检查标准差,它们大致相同,1233.771234.96

这有什么意义?我原以为带有function 对象的第二个版本会比模板版本慢。

Here整个测试可以在GDB上运行。

【问题讨论】:

  • 你是如何编译你的程序的?特别是启用了哪些优化?智能优化器可以转换您的代码以呈现差异,没有任何优化可以告诉我们任何性能。
  • 我使用了-O2。当然会涉及到编译器优化,我想在主要问题中提到它但后来忘记了。
  • 查看您的两个程序生成的程序集。它们可能是相同的。
  • 这有什么意义?我的第一个猜测是:你的假设不正确。

标签: c++ c++11 templates functional-programming function-object


【解决方案1】:

我知道std::function 是用类型擦除习语实现的。类型擦除是一种方便的技术,但作为一个缺点,它需要在堆上存储底层对象的寄存器(某种数组)。

类型擦除不一定需要堆分配。在这种情况下,std::function 的实现很可能不必进行任何堆分配,因为 lambda 不会捕获任何变量。因此,std::function 只需将函数指针存储在对象本身中,而不是在堆分配的内存中。

除此之外,即使std::function 确实做了堆分配,一些编译器甚至可能elide those heap allocations

最后但同样重要的是,虽然堆分配比堆栈分配更昂贵,但如果您只需要在整个程序期间在堆上分配一次,您可能不会注意到任何时间差异因此分配。

【讨论】:

  • GCC 对 std::function 的实现不需要为最大 16 字节的可调用对象分配堆(在 x86-64 上),而是使用内部预分配缓冲区。 Rough sketch on how it's implemented in GCC
  • 所以,底线:无论是由于编译器优化,还是堆分配的最小影响(如果有的话,以防可调用量很小),std::function 与模板调用一样快?跨度>
  • @Giogre 更准确的说法是 可以 一样快。不能保证总是如此。
  • 有趣。我在这个网站上看到很多人说“std::function 很贵”。好吧,只是展示一下,不要相信你读到的一切。
猜你喜欢
  • 1970-01-01
  • 2016-07-26
  • 1970-01-01
  • 2015-10-08
  • 1970-01-01
  • 2018-05-15
  • 1970-01-01
  • 1970-01-01
  • 2017-02-24
相关资源
最近更新 更多