【问题标题】:std::bind vs lambda performancestd::bind 与 lambda 性能
【发布时间】:2014-07-20 16:53:10
【问题描述】:

我想为一些函数的执行计时,我给自己写了一个助手:

using namespace std;
template<int N = 1, class Fun, class... Args>
void timeExec(string name, Fun fun, Args... args) {

    auto start = chrono::steady_clock::now();

    for(int i = 0; i < N; ++i) {
        fun(args...);
    }

    auto end = chrono::steady_clock::now();

    auto diff = end - start;
    cout << name << ": "<< chrono::duration<double, milli>(diff).count() << " ms. << endl;
}

我认为以这种方式为成员函数计时,我必须使用 bind 或 lambda,我想看看哪个对性能的影响较小,所以我这样做了:

const int TIMES = 10000;
timeExec<TIMES>("Bind evaluation", bind(&decltype(result)::eval, &result));
timeExec<1>("Lambda evaluation", [&]() {
    for(int i = 0; i < TIMES; ++i) {
        result.eval();
    }
});

结果是:

Bind evaluation: 0.355158 ms.
Lambda evaluation: 0.014414 ms.

我不知道内部原理,但我认为 lambda 不可能比 bind 更好。我能想到的唯一合理的解释是编译器优化了 lambda 循环中的后续函数评估。

你会怎么解释?

【问题讨论】:

  • 可以内联 lambda 主体。绑定表达式可能不可内联。检查机器代码以确保。也可以试试Fun &amp;&amp; fun
  • 还有一点:我是在学习表情模板的时候想到的。我有一个可以在运行时或编译时评估的计算图。所描述的行为发生在编译时评估的情况下,而不是运行时。
  • 您确实需要检查程序集以了解发生了什么。
  • 这不就是timeExec&lt;TIMES&gt; vs. timeExec&lt;1&gt;吗?
  • 这绝对不是苹果对苹果的比较。我很好奇如果你这样做会发生什么timeExec&lt;TIMES&gt;("Lambda evaluation", [&amp;] { result.eval(); })

标签: c++ caching c++11 lambda bind


【解决方案1】:

我认为 lambda 不可能比 bind 更好。

这是一个相当成见。

Lambda 被绑定到编译器内部,因此可以找到额外的优化机会。此外,它们旨在避免效率低下。

但是,这里可能没有发生编译器优化技巧。可能的罪魁祸首是要绑定的参数bind(&amp;decltype(result)::eval, &amp;result)。您正在传递一个指向成员函数 (PTMF) 的指针和一个对象。与 lambda 类型不同,PTMF 不捕获实际调用的函数;它只包含函数签名(参数和返回类型)。慢循环使用的是间接分支函数调用,因为编译器无法通过常量传播解析函数指针。

如果您将成员 eval() 重命名为 operator () () 并删除 bind,那么显式对象的行为本质上将类似于 lambda,并且性能差异应该会消失。

【讨论】:

  • 这是否适用于所有绑定变体,例如std::mem_fn?有时在std::all_ofstd::any_of 等内容中使用std::mem_fn 会更短。
【解决方案2】:

我已经测试过了。我的结果显示,Lambda 实际上比 bind 更快

这是代码(请不要看样式):

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

using namespace std;
using namespace chrono;
using namespace placeholders;

typedef void SumDataBlockEventHandler(uint8_t data[], uint16_t len);

class SpeedTest {
    uint32_t sum = 0;
    uint8_t i = 0;
    void SumDataBlock(uint8_t data[], uint16_t len) {
        for (i = 0; i < len; i++) {
            sum += data[i];
        }
    }
public:
    function<SumDataBlockEventHandler> Bind() {
        return bind(&SpeedTest::SumDataBlock, this, _1, _2);
    }
    function<SumDataBlockEventHandler> Lambda() {
        return [this](auto data, auto len)
        {
            SumDataBlock(data, len);
        };
    }
};

int main()
{
    SpeedTest test;
    function<SumDataBlockEventHandler> testF;
    uint8_t data[] = { 0,1,2,3,4,5,6,7 };

#if _DEBUG
    const uint32_t testFcallCount = 1000000;
#else
    const uint32_t testFcallCount = 100000000;
#endif
    uint32_t callsCount, whileCount = 0;
    auto begin = high_resolution_clock::now();
    auto end = begin;

    while (whileCount++ < 10) {
        testF = test.Bind();
        begin = high_resolution_clock::now();
        callsCount = 0;
        while (callsCount++ < testFcallCount)
            testF(data, 8);
        end = high_resolution_clock::now();
        cout << testFcallCount << " calls of binded function: " << duration_cast<nanoseconds>(end - begin).count() << "ns" << endl;

        testF = test.Lambda();
        begin = high_resolution_clock::now();
        callsCount = 0;
        while (callsCount++ < testFcallCount)
            testF(data, 8);
        end = high_resolution_clock::now();
        cout << testFcallCount << " calls of lambda function: " << duration_cast<nanoseconds>(end - begin).count() << "ns" << endl << endl;
    }
    system("pause");
}

控制台结果(优化发布):

100000000 calls of binded function: 1846298524ns
100000000 calls of lambda function: 1048086461ns

100000000 calls of binded function: 1259759880ns
100000000 calls of lambda function: 1032256243ns

100000000 calls of binded function: 1264817832ns
100000000 calls of lambda function: 1039052353ns

100000000 calls of binded function: 1263404007ns
100000000 calls of lambda function: 1031216018ns

100000000 calls of binded function: 1275305794ns
100000000 calls of lambda function: 1041313446ns

100000000 calls of binded function: 1256565304ns
100000000 calls of lambda function: 1031961675ns

100000000 calls of binded function: 1248132135ns
100000000 calls of lambda function: 1033890224ns

100000000 calls of binded function: 1252277130ns
100000000 calls of lambda function: 1042336736ns

100000000 calls of binded function: 1250320869ns
100000000 calls of lambda function: 1046529458ns

我已在 Visual Studio Enterprise 2015 下以完全优化 (/Ox) 的发布模式和禁用优化的调试模式编译它。结果证实 lambda 比我的笔记本电脑上的绑定更快(Dell Inspiron 7537、Intel Core i7-4510U 2.00GHz、8GB RAM)。

任何人都可以在您的计算机上验证这一点吗?

【讨论】:

  • OnIntel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz - 32GB Ram - clang++-4.0 libstdc++ lambda 是绑定时间的 1/3。使用 -O3 编译时,bind 和 lambda 几乎相等,但 bind 仍然慢 5%。在不展开循环的情况下进行编译可以确认结果。
  • AMD Ryzen 5 3600 16 GB RAM - VS Release x64:Lambda 并不总是更快,但大多数情况下 lambda 比绑定提高了 >5%。
猜你喜欢
  • 1970-01-01
  • 2014-03-25
  • 1970-01-01
  • 2020-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多