【问题标题】:How to get an objective evaluation of the execution time of a C++ code snippet?如何客观评估 C++ 代码片段的执行时间?
【发布时间】:2014-08-25 23:42:33
【问题描述】:

我正在关注这篇文章How to Calculate Execution Time of a Code Snippet in C++,这篇文章中给出了一个很好的解决方案,用于计算代码 sn-p 的执行时间。但是,当我用这个方案来测量我的代码sn-p在linux中的执行时间时,我发现我运行程序的每样东西,方案给出的执行时间都不一样。所以我的问题是如何客观地评估执行时间。客观评估对我来说很重要,因为我使用以下方案来评估同一任务的不同实现:

void main()
{
int64 begin,end; 
begin = GetTimeMs64();
execute_my_codes_method1();
end = GetTimeMs64();
std::cout<<"Execution time is "<<end-begin<<std::endl;
}

首先,我运行上面的代码来获取第一个方法的执行时间。之后,我将通过调用execute_my_codes_method2() 来更改上述代码,并获取第二种方法的执行时间。

 void main()
    {
    int64 begin,end; 
    begin = GetTimeMs64();
    execute_my_codes_method2();//execute_my_codes_method1();
    end = GetTimeMs64();
    std::cout<<"Execution time is "<<end-begin<<std::endl;
    }

通过比较不同的执行时间,我希望比较这两种不同实现的效率。

我之所以更改代码并运行不同的实现,是因为很难在一个程序中顺序调用它们。因此,对于同一个程序,在不同的时间运行它会导致不同的执行时间,这意味着用计算出的执行时间来比较不同的实现方法是没有意义的。关于这个问题有什么建议吗?谢谢。

【问题讨论】:

  • 多次调用测量和统计数据以获得更可靠和可比较的结果。仅使用单个调用进行测试可能取决于与您的程序代码完全无关的许多因素。
  • 最重要的是,在玩具环境中运行的代码可能与在真实环境中运行的代码执行不同。例如,在实际环境中,代码可能会多次运行,它访问的数据可能在缓存中,也可能不在缓存中,它使用的表或代码可能会遇到不同的缓存未命中,分支预测缓存可能会被不同的耗尽代码,其他线程等。完美是无法达到的,你所能做的就是努力。
  • @πάνταῥεῖ 感谢您的 cmets。我可以使用 valgrind/callgrind 来分析程序,以便找到程序的瓶颈。如果我对 valgrind/callgrind 很了解,如果它们不在同一个程序中,则很难比较两种方法。
  • @feelfree 您不能将运行这些功能与单元测试用例(甚至可以自动执行重复)和单元测试器隔离开来吗?这两种方法不能在同一个可执行文件中运行的真正问题是什么?
  • @πάνταῥεῖ 谢谢,我仔细检查了代码,并且可以通过一些额外的工作在同一个可执行文件中运行它。对我来说,我只想快速找到最好的实现方法。考虑到运行 valgrind/callgrind 可能需要一些时间,我只是想知道使用执行时间来比较不同的实现。我可以快速选择执行时间最短的那个。

标签: c++ benchmarking


【解决方案1】:

测量单个调用的执行时间对于判断性能改进毫无用处。影响函数实际执行时间的因素太多。如果你要测量时间,你应该多次调用函数测量时间并建立测量执行时间的统计平均值

void main() {
    int64 begin = 0, end = 0; 
    begin = GetTimeMs64();
    for (int i = 0; i < 10000; ++i) {
        execute_my_codes_method1();
    }
    end = GetTimeMs64();
    std::cout<<"Average execution time is "<< (end - begin) / 10000 << std::endl;
}

此外,与上面显示的不同,预先为您的功能进行单元测试(使用像Google Test这样的体面的测试框架)将使您更快更容易地做出快速判断。

您不仅可以确定测试用例的运行频率(收集统计数据以计算平均时间),单元测试还可以证明所需/现有的功能和输入/输出的一致性没有被破坏替代实现。

作为一个额外的好处(正如您提到的难以按顺序运行这两个函数),大多数单元测试框架允许有一个 SetUp()TearDown() 方法,它们在运行测试用例之前/之后执行.因此,您可以轻松地为每个测试用例运行提供一致的谓词状态或不变条件。


作为进一步的选择,您可以使用通过代码检测工作的分析工具,而不是自己进行测量以收集统计数据。一个很好的例子是GCC's gprof。我认为收集了有关调用每个底层函数的频率以及执行时间的信息。稍后可以使用该工具分析这些数据,以在您的实现中找到潜在的bottlenecks

此外,如果您决定在未来提供单元测试,您可能希望确保您的所有代码路径与各种输入数据情况相关,并被您的测试用例很好地覆盖。一个很好的例子,关于如何做到这一点,是GCC's gcovinstrumentation。要分析收集到的有关代码覆盖率的信息,您可以使用lcov,它可以非常漂亮和全面地可视化结果。

【讨论】:

  • 不需要每次迭代都花时间,只需要整个循环的时间。
猜你喜欢
  • 2010-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-05
  • 1970-01-01
相关资源
最近更新 更多