Скорость выполнения кода с объектом «функция» по сравнению с использованием шаблонных функций

Я знаю, что std::function реализован с помощью идиомы стирание типа. Стирание типов — удобный метод, но его недостатком является необходимость хранения в куче регистра (своего рода массива) базовых объектов.

Следовательно, при создании или копировании объекта 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.

Затем я ввожу объект function в main, а также специализацию шаблона для 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.77 против 1234.96.

Какой в ​​этом смысл? Я ожидал, что вторая версия с объектом function будет медленнее, чем версия шаблона.

Здесь весь тест можно запустить на GDB.

Как вы скомпилировали свою программу? В частности, какие оптимизации включены? Умный оптимизатор может преобразовать ваш код, чтобы сделать разницу спорной, и никакая оптимизация ничего не говорит нам о производительности.

YSC 17.05.2022 20:36

Я использовал -O2. Конечно, будут задействованы оптимизации компилятора, я хотел упомянуть об этом в основном вопросе, но потом забыл.

Giogre 17.05.2022 20:40

Проверьте сборку, сгенерированную вашими двумя программами. Они могут быть одинаковыми.

YSC 17.05.2022 20:44
Какой в ​​этом смысл? Мое первое предположение: ваше предположение было неверным.
Eljay 17.05.2022 20:48
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Клиент для URL-адресов, cURL, позволяет взаимодействовать с множеством различных серверов по множеству различных протоколов с синтаксисом URL.
Четыре эффективных способа центрирования блочных элементов в CSS
Четыре эффективных способа центрирования блочных элементов в CSS
У каждого из нас бывали случаи, когда нам нужно отцентрировать блочный элемент, но мы не знаем, как это сделать. Даже если мы реализуем какой-то...
0
4
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

I know that std::function is implemented with the type erasure idiom. Type erasure is a handy technique, but as a drawback it needs to store on the heap a register (some kind of array) of the underlying objects.

Стирание типа не обязательно требует выделения кучи. В этом случае, вероятно, реализация std::function не должна будет выполнять выделение кучи, поскольку лямбда не фиксирует никаких переменных. Следовательно, std::function должен хранить указатель на функцию только в самом объекте, а не в памяти, выделенной в куче.

Кроме того, даже если std::function действительно выделяет кучу, некоторые компиляторы могут даже исключить эти распределения кучи.

И последнее, но не менее важное: хотя выделение кучи дороже, чем выделение стека, если вам нужно выделить что-то в куче только один раз на протяжении всей программы, вы, вероятно, не заметите никакой разницы во времени из-за этого выделения.

Реализация GCC std::function не требует выделения кучи для вызываемых объектов размером до 16 байт (на x86-64), вместо этого используется внутренний предварительно выделенный буфер. Грубый набросок того, как это реализовано в GCC

Jonathan S. 17.05.2022 20:51

Итак, итог: либо из-за оптимизации компилятора, либо из-за минимального влияния распределения кучи (если оно вообще есть, если вызываемый объект мал), std::function так же быстр, как вызовы шаблона?

Giogre 17.05.2022 21:55

@Giogre было бы точнее сказать, что могу будет таким же быстрым. Нет гарантии, что так будет всегда.

Kyle 17.05.2022 22:02

@ Кайл Это решает, спасибо.

Giogre 17.05.2022 22:21

Интересный. Я видел, как многие люди говорят «std::function дорого» на этом сайте. Ну, просто показывает, не верь всему, что читаешь.

Paul Sanders 18.05.2022 00:07

Другие вопросы по теме