为什么不总是使用编译器优化?

时间:2011-10-22 05:23:00

标签: compiler-construction compiler-optimization

One of the questions that I asked some time ago有未定义的行为,因此编译器优化实际上导致程序中断。

但是如果代码中没有未定义的行为,那么有没有理由不使用编译器优化?我理解有时候,出于调试目的,可能不需要优化代码(如果我错了,请纠正我)。除此之外,在生产代码上,为什么不总是使用编译器优化?

此外,是否有理由使用-O代替-O2-O3

9 个答案:

答案 0 :(得分:29)

如果没有未定义的行为,但是存在明确的破坏行为(确定性正常错误,或者像竞争条件一样不确定),关闭优化是值得的,因此您可以使用调试器逐步执行代码。

通常,当我达到这种状态时,我喜欢组合:

  1. 调试构建(无优化)并逐步执行代码
  2. 向诊断报表汇总到stderr,以便我可以轻松跟踪运行路径
  3. 如果错误更加狡猾,我会拔出 valgrind drd ,并根据需要添加单元测试,以隔离问题并确保在找到问题时,解决方案按预期工作。

    在极少数情况下,调试代码有效,但发布代码失败。当这种情况发生时,几乎总是,问题出现在我的代码中;发布版本中的积极优化可以揭示由于对临时工具的生命周期误解等导致的错误......但即使在这种情况下,调试构建也有助于隔离问题。

    简而言之,专业开发人员构建和测试调试(非优化)和发布(优化)二进制文件的原因有很多。恕我直言,同时拥有调试和发布版本的单元测试将为您节省大量的调试时间。

答案 1 :(得分:23)

编译器优化有两个缺点:

  1. 优化几乎总是重新排列和/或删除代码。这会降低调试器的效率,因为源代码和生成的代码之间不再存在1对1的对应关系。堆栈的某些部分可能会丢失,并且逐步执行指令可能会以违反直觉的方式跳过部分代码。
  2. 执行优化通常很昂贵,因此在启用优化的情况下编译需要更长的时间才能进行编译。在编译代码时很难做任何有效的工作,因此显然缩短编译时间是一件好事。
  3. -O3执行的某些优化可能会导致更大的可执行文件。在某些生产代码中可能不需要这样做。

    不使用优化的另一个原因是您使用的编译器可能包含仅在执行优化时才存在的错误。没有优化的编译可以避免这些错误。如果您的编译器确实包含错误,则更好的选择可能是报告/修复这些错误,更改为更好的编译器,或者编写完全避免这些错误的代码。

    如果您希望能够对已发布的生产代码执行调试,那么不优化代码也是一个好主意。

答案 2 :(得分:8)

3个理由

  1. 它混淆了调试器,有时候
  2. 它与某些代码模式不兼容
  3. 不值得:缓慢或错误,或占用太多内存,或产生太大的代码。
  4. 在案例2中,想象一些故意更改指针类型的操作系统代码。优化器可以假设无法引用错误类型的对象,并生成代码来更改寄存器中的内存值,并获得“错误的” 1 答案。

    案例3是一个有趣的问题。有时优化程序会使代码变小,但有时会使代码更大。大多数程序都不受CPU限制,即使是那些程序,只有10%或更少的代码实际上是计算密集型的。如果优化器有任何缺点,那么只有不到10%的程序才能获胜。

    如果生成的代码较大,则它对缓存不太友好。对于在微小环路中具有O(n 3 )算法的矩阵代数库,这可能是值得的。但对于具有更典型时间复杂度的事物,溢出缓存实际上可能会使程序变慢。通常情况下,优化器可以针对所有这些内容进行调整,但是如果程序是Web应用程序,那么,如果编译器只执行通用程序并且允许开发人员不打开它,那么它肯定会更适合开发人员。花哨的技巧Pandora's box.


    1。这些程序通常不符合标准,因此优化器在技术上“正确”,但仍然没有按开发人员的意图执行。

答案 3 :(得分:3)

原因是您开发了一个应用程序(调试版本),并且您的客户运行完全不同的应用程序(发布版本)。如果测试资源很少和/或使用的编译器不是很流行,我会禁用发布版本的优化。

MS在其MSVC x86编译器中发布了许多针对优化错误的修补程序。幸运的是,我从未在现实生活中遇到过一个。但其他编译器并非如此。 MS Embedded Visual C ++中的SH4编译器非常错误。

答案 4 :(得分:2)

简单。编译器优化错误。

答案 5 :(得分:2)

刚好发生在我身上。由swig生成的用于连接Java的代码是正确的,但不能在gcc上使用-O2。

答案 6 :(得分:2)

我看到的两个重要原因来自浮点数学和过于激进的内联。前者是由于C ++标准对浮点数学的定义极差。许多处理器使用80位精度执行计算,例如,当值被放回主存储器时,仅下降到64位。如果一个例程的版本经常将该值刷新到内存中,而另一个版本只在最后一次获取该值,则计算结果可能会略有不同。只是调整该例程的优化可能比重构代码对差异更强大更好。

内联可能会有问题,因为从本质上讲,它通常会导致更大的目标文件。也许这种增加是代码大小因实际原因而不可接受:例如,它需要适合具有有限内存的设备。或者代码大小的增加可能导致代码变慢。如果一个例程变得足够大以至于它不再适合缓存,那么最终的缓存未命中可能会快速超过首先提供的内联优势。

我经常听到那些在多线程环境中工作时会关闭调试并立即遇到大量新bug的人,因为新发现的竞争条件等等。优化器刚刚在这里透露了潜在的错误代码,因此在响应中将其关闭可能是不明智的。

答案 7 :(得分:0)

基于程序不会执行 X 的想法的优化在处理不涉及执行 X 的任务时很有用,但在执行可以最好地完成的任务时充其量只会适得其反通过做 X。

由于 C 语言有多种用途,因此该标准特意允许专为特殊用途而设计的编译器对程序行为做出假设,从而使它们不适用于许多其他用途。标准的作者允许实现扩展语言的语义,通过指定它们在标准没有强加要求的情况下的行为方式,并期望质量实现会在客户发现它有用的情况下寻求这样做,而不需要考虑标准是否要求他们这样做。

需要执行标准未预期或未满足的任务的程序通常需要利用其行为由许多实现定义但未由标准强制执行的结构。这样的程序并没有被“破坏”,而只是用标准不要求所有实现都支持的方言编写。

答案 8 :(得分:-10)

一个例子是短路布尔评估。类似的东西:

if (someFunc() && otherFunc()) {
  ...
}

“智能”编译器可能会意识到someFunc将始终因某种原因返回false,使整个语句的计算结果为false,并决定不调用otherFunc来节省CPU时间。但是如果otherFunc包含一些直接影响程序执行的代码(可能会重置全局标志或其他东西),它现在将不会执行该步骤并且您的程序进入未知状态。