std:: bind vs лямбда-производительность

Я хотел время выполнения нескольких функций, и я написал себе помощник:

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.

Я не знаю внутренних органов, но я предполагаю, что лямбда не может быть лучше, чем bind. Единственное правдоподобное объяснение, которое я могу придумать, - это компилятор оптимизация последующих оценок функций в цикле лямбды.

как бы вы это объяснили?

2 ответов


Я предполагаю, что лямбда не может быть лучше, чем связать.

Это довольно предвзятое.

Lambdas привязаны к внутренним компонентам компилятора, поэтому могут быть найдены дополнительные возможности оптимизации. Более того, они предназначены для того, чтобы избежать неэффективности.

однако, вероятно, здесь нет трюков оптимизации компилятора. Вероятным виновником является аргумент bind,bind(&decltype(result)::eval, &result). Вы передаете указатель на функцию-член (PTMF) и объект. В отличие от лямбда-типа, PTMF не фиксирует, какая функция фактически вызывается; он содержит только сигнатуру функции (типы параметров и возвращаемых значений). Медленный цикл использует косвенный вызов функции ветви, поскольку компилятору не удалось разрешить указатель функции путем постоянного распространения.

если переименовать элемент eval() to operator () () и избавиться от bind, то явный объект будет по существу вести себя как лямбда и разница в производительности должен исчезнуть.


я протестировал его. Мои результаты показывают, что лямбда на самом деле быстрее, чем 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) и в режиме отладки с отключенной оптимизацией. Результаты подтверждают, что лямбда быстрее, чем привязка на моем ноутбуке (Dell Inspiron 7537, Intel Core i7-4510U 2.00 GHz, 8GB RAM).

может ли кто-нибудь проверить это на вашем компьютере?