C ++ - 执行速度测试的正确方法是什么?

时间:2013-02-04 00:52:40

标签: c++ performance compiler-construction dead-code optimization

我搜索了许多询问相关信息的问题,但答案并没有与我想要的答案完全匹配。我会尽力解释这个问题。

基本上,当在发布模式下运行代码时,编译器似乎删除了大多数冗余或死代码的代码。所以它最终没有检查任何东西。一些修复是将代码存储到某个变量,但是编译只是删除循环并存储它看起来的最后一个增量。

现在我确实希望进行优化以改进所使用的代码,但我仍然想要它最初做的所有事情。如果我让它循环代码100,000次,我希望它实际执行代码100,000次。我不确定如何在Visual Studio 2010上修改编译器,以便在发布模式下进行编译时进行最小化优化。我非常想准确地计时,但我不确定如何准确地计时。

起初我认为在没有调试的情况下在调试中运行可能会解决问题,而且由于结果与Java应用程序的结果相匹配,因此非常似乎,但是当在发布模式下运行时,结果会让我感到困惑。我不确定C ++在优化中是否会好得多,或者是否已经更改了大量代码。

有没有办法可以分解代码并查看编译编译代码的内容?这将是我希望看到的另一个测试,但我对这些东西知之甚少,并且在正确方向上的任何事情都会非常感激。好的,感谢任何能够理解我要求的人。我很乐意回答有关手头问题的任何误解或不确定性的任何问题。

5 个答案:

答案 0 :(得分:4)

因此,为了避免编译器优化掉所有代码,您需要确保“使用”代码中的结果。

另一个技巧是将测试中的代码放在一个单独的文件中,因此编译器无法内联您的“函数在文件之外”(除非您启用“整个程序优化”)。

我经常使用函数指针 - 不是因为它会阻止优化[虽然它经常会这样做],但是因为它为使用相同基本的几个测试提供了良好的基础“测量它花了多长时间并打印出结果“,有一张桌子,看起来有点像这样:

 typedef void (*funcptr)(void);

 #define FUNC(f) { f, #f }

 struct func_entry
 {
      funcptr func;
      const char *name;
 };
 func_entry func_table[] = 
 {
      FUNC(baseline),
      FUNC(better1),
      FUNC(worse1),
 };

 void do_benchmark()
 {
     for(int i = 0; i < sizeof(func_table)/sizeof(func_table[0]); i++)
     {
          timestamp t = now();
          func_table[i].func();
          t = now() - t;

          printf("function %s took %8.5fs\n", func_table[i].name, 
                 timestamp_to_seconds(t));
     }
 }

显然,您需要使用一些合适的时间取值函数替换now(),并使用该函数的相关类型替换timestamp,并使用有效的内容替换timestamp_to_seconds。 。

答案 1 :(得分:0)

您不仅需要使用循环中定时调用的结果,还应使用每次迭代的结果。在这里,您要确保使用每个循环的结果,但不要超出您尝试测试的开销。

一种典型的方法是累积所有方法调用的总和,用于返回整数值的东西。这可以扩展到不通过调用返回int的其他方法返回int的方法。例如,如果您的方法创建std::string s,请在返回的字符串上调用size(),这应该非常快。在C ++中,您可以使用address-of运算符&作为将几乎任何内容转换为整数的快速方法。

在某些情况下,编译器可能仍然可以看到你的技巧并将实际方法提升出循环,这会退化为添加一堆值甚至是一个大的乘法。

您可以通过迭代在运行时生成的某种输入来避免这种情况 - 编译器无法将循环折叠起来。使用函数指针也可以工作,但增加了额外的开销,一些编译器(以及将来会有更多)可能仍然可以查看它们。

你一直这样做,你必须问问自己你在测量什么。在循环中测量的非常小的方法不一定能够很好地指示当循环更复杂时它们在现实生活中的表现。这适用于优化范围的两个方面 - 例如,&#34;提升&#34;您在基准测试中试图避免的可能实际上是在实际代码中发生,因此您的基准测试过于悲观。相反,像你的基准测试中总是命中L1的8K查找表这样的东西可能会在实际代码中引发一堆未命中。

总结 - 微基准测试是一个谨慎使用的工具 - 一旦你理解了如何防止你认为在实践中不切实际的优化,你就可以测量某些东西但是你应该总是评估一些真实的 - 世界用例作为一种健全性检查(理解微基准测试的巨大差异总是转化为大型程序中的小得多的改进,其中测试方法是运行时的一小部分开始)。

答案 2 :(得分:0)

我所做的是有一个benchmark()函数,它位于DLL中,并传递一个函数指针。阻止编译器优化基准测试循环的最佳方法是使其无法执行此操作,并将其放在单独的DLL中绝对可以防止这种情况。随着LTCG变得普遍,单独的翻译单元将不再发挥作用。

首先:一些快速设置。将线程的亲和性设置为单个核心,并赋予其高优先级。这将防止由于上下文切换或缓存抖动导致的许多可能的变化。并且不要忘记使用像QueryPerformanceCounter这样的高精度计时器来计时 - 这些计时器不依赖于系统时间。

接下来,在循环中调用函数指针,直到两秒钟过去。这将预热缓存中的代码/数据,让您大致了解其速度,并允许您自动导出合理的循环计数。我选择一个循环计数,它将在1秒内完成。

接下来,实际的基准测试:维护一个计数器,每当一个循环没有提高前一个循环的速度时,该计数器就会递增。当计数器达到某个设定数量时,停止并假设我们找到了最佳时间。当速度提高时,计数器会重置。

请注意,这不会像正确的探查器那样告诉您要优化的内容,但如果您的唯一目标是将一段代码与另一段代码进行比较,则应该提供非常准确的结果。

答案 3 :(得分:0)

这取决于您的代码尝试执行的操作以及您希望它的行为方式。

  1. 您的代码是在进行I / O(网络,磁盘,音频等等)吗?
  2. 是多线程的吗?
  3. 很多处理内存?
  4. 您是否处于时间关键循环中?
  5. 处理器密集吗?
  6. 通常,您希望在发布模式下运行并使用分析器来查看代码在有效的使用/测试用例中的执行情况。即便如此,你需要知道what profiler to use,这又取决于你要解决的问题。

    最重要的是,如果没有问题,请不要去寻找问题。相信你的编译器。这些天试图超越你的编译器是一项徒劳(和愚蠢)的练习。您的探查器只会吐出数据。您需要知道要查找和解释的内容。

答案 4 :(得分:-2)

假设您使用的是Visual Studio(Visual C ++),请将其设置为Debug profile,右键单击该项目 - &gt;属性并转到C / C ++ - &gt;优化。确保它已被禁用。

然后您可以使用秒表或unix程序time来计算程序运行的时间。

当然,还有更复杂的分析性能的方法 - 例如使用分析器。