2

我搜索了许多询问相关信息的问题,但答案并不完全符合我想要的答案。我会尽力解释这个问题。

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

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

起初我认为在不调试的情况下在调试中运行可能会解决问题,因为结果与 Java 应用程序的结果相匹配,但在发布模式下运行时结果快得离谱,这让我感到困惑。我不确定 C++ 在优化方面是否更好,或者是否更改了大量代码。

有没有办法也可以反汇编代码并查看编译器将代码编译成什么?这将是我希望看到的另一项测试,但我对这些东西知之甚少,任何朝着正确方向的东西都将不胜感激。好吧,感谢任何能理解我要求的人。我很乐意回答有关手头问题的任何误解或不确定性的任何问题。

4

5 回答 5

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有效的东西来替换......

于 2013-02-04T01:07:04.457 回答
0

您不仅需要使用在循环中计时的调用结果,而且还应该使用每次迭代的结果。在这里,您尝试确保使用每个循环的结果,但不要在您尝试测试的内容之外施加过多的开销。

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

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

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

在你这样做的整个过程中,你必须问自己你真正测量的是什么。在循环中测量的非常小的方法不一定能很好地说明当循环更复杂时它们在现实生活中的表现。这适用于优化范围的两侧 - 例如,您在基准测试中试图避免的“提升”实际上可能发生在实际代码中,因此您的基准测试过于悲观。相反,在你的基准测试中总是达到 L1 的 8K 查找表之类的东西可能会在实际代码中导致大量未命中。

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

于 2013-02-04T01:22:51.990 回答
0

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

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

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

接下来,实际的基准测试:维护一个计数器,每次循环不会提高前一个循环的速度时,该计数器就会增加。当计数器达到某个设定值时,停止并假设我们已经找到了最佳时间。当速度提高时,计数器复位。

请注意,这不会像适当的分析器那样告诉您要优化什么,但如果您的唯一目标是将一段代码与另一段代码进行比较,它应该会给出非常准确的结果。

于 2013-02-04T01:39:30.630 回答
0

这取决于您的代码试图做什么以及您期望它的行为方式。

  1. 您的代码是否在执行 I/O(网络、磁盘、音频等)?
  2. 它是多线程的吗?
  3. 它处理内存很多吗?
  4. 您是否处于时间紧迫的循环中?
  5. 它是处理器密集型的吗?

通常,您希望在发布模式下运行并使用分析器来查看您的代码在有效的使用/测试用例中的执行情况。即使这样,您也需要知道要使用什么分析器,这取决于您要解决的问题。

最重要的是,当没有问题时不要去寻找问题。相信你的编译器。这些天试图超越你的编译器是一个非常徒劳(和愚蠢)的练习。您的分析器只会吐出数据。由您决定要查找和解释什么。

于 2013-02-04T01:39:55.783 回答
-3

假设您使用的是 Visual Studio (Visual C++),将其设置为调试配置文件,右键单击项目 -> 属性并转到 C/C++ -> 优化。确保它被禁用。

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

当然还有更复杂的分析性能的方法——比如使用分析器。

于 2013-02-04T00:56:17.257 回答