使用 C++14 lambda 测量任意函数的执行时间

Posted

技术标签:

【中文标题】使用 C++14 lambda 测量任意函数的执行时间【英文标题】:Measure execution time of arbitrary functions with C++14 lambda 【发布时间】:2019-05-13 06:19:31 【问题描述】:

Scott Meyer 的《Effective Modern C++》一书中的第 24 项让我很兴奋。他提到了编写 C++14 lambda 来记录任意函数调用所用时间的可能性。

我仍处于学习 C++14 功能的早期阶段。我的尝试(Main.cpp)看起来像这样来测量成员函数调用的时间:

#include <chrono>
#include <iostream>

auto measure = [](auto&& function, auto&&... parameters) -> decltype(function)

    const std::chrono::steady_clock::time_point startTimePoint =
    std::chrono::steady_clock::now();

    const auto returnValue = std::forward<decltype(function)>(function)(
            std::forward<decltype(parameters)>(parameters)...);

    const std::chrono::steady_clock::time_point stopTimePoint =
    std::chrono::steady_clock::now();

    const std::chrono::duration<double> timeSpan = std::chrono::duration_cast<
    std::chrono::duration<double>>(stopTimePoint - startTimePoint);

    std::cout << "Computation took " << timeSpan.count()
    << " seconds." << std::endl;

    return returnValue;
;

class Test

public:

    int computation(double dummy)
    
        std::cout << "Received " << dummy << ". Computing..." << std::endl;

        return 123;
    
;

int main(int, char**)

    Test instance;

    using Function = int (Test::*)(double);
    Function function = instance.computation;

    int result = measure(function, 1.0);

    std::cout << "Result: " << result << std::endl;

    return 0;

我收到以下编译错误:

..\src\Main.cpp: In function 'int main(int, char**)':
..\src\Main.cpp:43:36: error: cannot convert 'int (Test::*)(double)' to 'int' in initialization
    int result = measure(function, 1.0);
                                                                        ^
..\src\Main.cpp: In instantiation of '<lambda(auto:1&&, auto:2&& ...)> [with auto:1 = int (Test::*&)(double); auto:2 = double; decltype (function) = int (Test::*&)(double)]':
..\src\Main.cpp:43:36:   required from here
..\src\Main.cpp:9:69: error: must use '.*' or '->*' to call pointer-to-member function in 'std::forward<int (Test::*&)(double)>((* & function)) (...)', e.g. '(... ->* std::forward<int (Test::*&)(double)>((* & function))) (...)'
    const auto returnValue = std::forward<decltype(function)>(function)(
                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        std::forward<decltype(parameters)>(parameters)...);
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                

显然我做错了,但我不知道如何做对。有谁能够帮我?非常感谢!

【问题讨论】:

你的 lambda 返回了错误的类型,查看std::result_of or std::invoke_result。您可能还想使用std::invoke。另请注意,时间测量具有一定的精度/开销(通常为数百纳秒),因此测量非常小的函数的运行时间可能无法提供相关数据。 你为什么想让measure成为一个lambda? @DanielLangr std::invoke 是c++17 的一部分。问题针对c++14... @DanielLangr 我尝试过类似using ReturnType = std::result_of&lt;function(parameters...)&gt;::type; 的操作,但我的编译器不接受它。你能给我另一个提示我使用std::result_of吗?谢谢! @n.m.如果它有效,它看起来很方便,我正在研究 C++11 和 C++14 的特性。 【参考方案1】:

有两种方法可以完成这项任务。

    接受一个函数(或函数对象),返回一个修改后的函数,该函数与原始函数执行相同的操作,并测量时间。返回的对象类型不能与接受的参数类型相同。它必须是 lambda(或自定义类类型,但 lambda 更简单)。实际测量是在调用 returned 对象时执行的。使用语法示例:

    result = measure(foo)(param1, param2);  // variant 1
    auto measured_foo = measure(foo);
    result = measured_foo(param1, param2);  // variant 2
    

    接受一个函数(或函数对象)它的参数,调用它并执行测量。返回类型是原始函数的类型。使用语法示例:

    result = measure(foo, param1, param2);
    

您的measure 最接近第二个变体,唯一的错误与它 是声明。这是正确的:

auto measure = [](auto&& function, auto&&... parameters) -> decltype(auto)

确切地说,这不是唯一的错误。如果被测函数返回一个引用,则返回类型将是错误的。要解决此问题,请替换

const auto returnValue = ...

decltype(auto) returnValue = ...

在 lambda 的主体中

您的程序(但不是measure 本身)的另一个问题是您尝试使用成员函数的方式。

Function function = instance.computation;

这行不通。使用 lambda 或 std::bind 创建绑定成员函数。关于在 *** 上正确的方法,有无数个问题(和很好的答案)。

Live demo(通过引用返回工作)。

如果您想要第一种方法来创建测量函数,方法如下:

auto measure = [](auto&& function) -> decltype(auto)

    return [=](auto&&... parameters) mutable -> decltype(auto) 

        const std::chrono::steady_clock::time_point startTimePoint = 
            std::chrono::steady_clock::now();

        decltype(auto) result = function(std::forward<decltype(parameters)>(parameters)...);

        const std::chrono::steady_clock::time_point stopTimePoint =
            std::chrono::steady_clock::now();

        const std::chrono::duration<double> timeSpan = std::chrono::duration_cast<
        std::chrono::duration<double>>(stopTimePoint - startTimePoint);

        std::cout << "Computation took " << timeSpan.count()
                << " seconds." << std::endl;

        return result;
    ;
;

Live demo(通过引用返回)。

特别注意decltype(auto) 的宗教用途。在第二个版本中还有mutable

【讨论】:

感谢您的精彩解释、现场演示,甚至提供了另一种方法!我会花很多时间研究你的代码,你可以肯定的! :)【参考方案2】:

不知道你想在那里做什么,但如果这是你想要做的,我在这里猜测我做了什么:

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

auto measure = [](auto function, auto&&... parameters) -> decltype(function(parameters...))

    const std::chrono::steady_clock::time_point startTimePoint =
    std::chrono::steady_clock::now();

    auto returnValue = function(parameters...);

    const std::chrono::steady_clock::time_point stopTimePoint =
    std::chrono::steady_clock::now();

    const std::chrono::duration<double> timeSpan = std::chrono::duration_cast<
    std::chrono::duration<double>>(stopTimePoint - startTimePoint);

    std::cout << "Computation took " << timeSpan.count()
    << " seconds." << std::endl;

    return returnValue;
;

class Test

public:

    int computation(double dummy)
    
        std::cout << "Received " << dummy << ". Computing..." << std::endl;

        return 123;
    
;

int main(int, char**)

    Test instance;

    auto func = std::bind(&Test::computation, &instance, std::placeholders::_1);

    int result = measure(func, 1.0);

    std::cout << "Result: " << result << std::endl;

    return 0;

【讨论】:

为什么你不知道? OP 正在探索 C++14 功能......这个答案应该提供解决编译错误的代码,并且可以顺利执行,干得好。但是,您需要添加一些解释,其他人可能不知道您要在这里做什么... :) 如果他写的话,我想做的就是实现一个函数,该函数接受实例参数等等等,我本来可以这样写的。有无数种方法可以正确地做同样的事情。你看我已经消除了所有的编译错误所以接下来会发生什么他会说不不我想这样使用它或者是的这就是我想要的:)你也看到我也在使用 C++14 特性所以在这里就是答案,你可以以此为例, Abdurrahim,如果问题不清楚,那么您应该发表评论并要求提供更多信息,而不是像您声称的那样发布基于假设的答案。我相信情况并非如此,所以,如果我是你,我会添加一些解释...... @Abdurrahim 非常感谢!这确实是我一直在努力做的。有两件事我不明白:Scott Meyers 将该函数作为通用参考 (auto&amp;&amp; function) 传递。你为什么决定不这样做? Scott Meyers 在我的初始代码中使用std::forward&lt;decltype(func)&gt;(func)(...) 实现了这样的函数调用。这比你的代码有什么优势吗? 此解决方案省略了函数及其参数的完美转发。 @BenjaminBihler 是的,它的优点是所有参数都通过原样,保留它们的值类别。如果没有完美转发,所有参数都将被视为左值。【参考方案3】:

如果您出于任何原因不喜欢使用成员函数的函数指针(请参阅here),好的旧宏可以帮助您。有些人建议尽量减少宏的使用,但在这种情况下,我发现它更直观、易读和容易。任何任意函数(包括调用返回类型的类的公共成员函数)都可以使用以下宏进行计时。

#define timefnrt(W, X, Z)\
    time_t V = time(NULL);\
    W = X;\
    time_t Y = time(NULL);\
    Z = difftime(Y, V);\
;

Live code demonstration

如果函数返回一个void,这样的函数可以这样计时:

#define timefnvoid(X, Z)\
    time_t V = time(NULL);\
    X;\
    time_t Y = time(NULL);\
    Z = difftime(Y, V);\
;

【讨论】:

以上是关于使用 C++14 lambda 测量任意函数的执行时间的主要内容,如果未能解决你的问题,请参考以下文章

如何测量 Linux 和 Windows 中函数的“用户”执行时间

C++14 中的递归 lambda 函数

Qt Lambda表达式的运用

如何传递和使用任意lambda函数作为参数[重复]

C++C++中的lambda表达式和函数对象

14.Python内置函数?