【问题标题】:Does C/C++ offer any guarantee on minimal execution time?C/C++ 是否提供最短执行时间的任何保证?
【发布时间】:2016-08-17 06:53:43
【问题描述】:

为什么编译器似乎对什么都不做也不消除它们的循环有礼貌?

C 标准是否要求循环需要一些时间?

例如,如下代码:

void foo(void) {
    while(1) {
        for(int k = 0; k < 1000000000; ++k);
        printf("Foo\n");
    }
}

运行速度比这个慢:

void foo(void) {
    while(1) {
        for(int k = 0; k < 1000; ++k);
        printf("Foo\n");
    }
}

即使使用-O3 优化级别。 我希望删除允许的空循环,从而在两个代码上获得相同的速度。

“所花费的时间”是应该由编译器保留的副作用吗?

【问题讨论】:

  • 什么是粗鲁的编译器?
  • 我认为 C/C++ 标准没有任何“运行时间”的概念。
  • 粗鲁的编译器是一种非常注意将代码中的任何“未定义行为”变成最糟糕的噩梦的编译器。
  • 这是一个关于 as-if 规则以及什么是可观察行为的体面问题。
  • 您只是测量启动开销或其他难以预测的开销。基本上是随机数。在提出此类问题之前,您应该咨询反汇编。

标签: c++ c language-lawyer compiler-optimization


【解决方案1】:

不,所花费的时间不算作受 as-if 规则保护的可观察行为:

[C++14: 1.8/5]: 执行格式良好的程序的一致实现应产生与具有相同程序和相同输入的抽象机的相应实例的可能执行之一相同的可观察行为。 但是,如果任何此类执行包含未定义的操作,则本国际标准对使用该输入执行该程序的实现没有要求(甚至不考虑第一个未定义操作之前的操作)。

[C++14: 1.5/8]: 符合要求的实现的最低要求是:

  • 对易失性对象的访问严格按照抽象机的规则进行评估。
  • 在程序终止时,写入文件的所有数据都应与根据抽象语义执行程序可能产生的结果之一相同。
  • 交互式设备的输入和输出动态应以这样一种方式发生,即在程序等待输入之前实际交付提示输出。什么构成交互式设备是由实现定义的。

这些统称为程序的可观察行为 [注:抽象语义和实际语义之间的更严格对应可能是由每个实现定义。 ——尾注]

这些循环可以合法地优化出来,事实上,在某些情况下,标准故意试图让这样做更容易:

[C++14: 1.10/24]: 实现可能假设任何线程最终都会执行以下操作之一:

  • 终止,
  • 调用库 I/O 函数,
  • 访问或修改易失性对象,或
  • 执行同步操作或原子操作。

[ 注意: 这旨在允许编译器转换,例如删除空循环,即使无法证明终止。 ——尾注]

您的编译器实际上可能是“礼貌”地注意到这些程序中循环的意图似乎是减慢重复文本输出的发射。 :)

【讨论】:

  • 令人讨厌的是,问题本身规定了“C 标准”。
  • 我希望规则改为指定执行循环所需的时间,即使是无限的,本身并不被视为副作用。这样的规则不会像上面引用的规则那样否定因果律。例如,给定do {x=expr_that_might_never_yield_1(...);} while (x != 1); printf("Done..."); if (x==1) printf("Hooray!");,我可以看到允许编译器在循环之前移动printf("Done..."),但不允许编译器假设x 会以某种方式等于1。
  • 我认为优化循环以通知程序员这是可能随时中断的脆弱代码会更“礼貌”。
  • @usr:编译器的工作不是手持/勺子。程序员只负责自己的学习,没有其他人。
【解决方案2】:

您没有指定编译器,但我们假设它是gcc

gcc not 确实删除了空循环,至少不是根据documentation。它包含以下文本:

从历史上看,GCC 没有删除“空”循环,假设您将其放入程序中的最可能原因是延迟,因此删除它们不会使实际程序运行得更快。

但是,如果空循环被优化器“清空”,它可以删除空循环,也就是说,如果循环包含优化器可以移出循环的代码,并且生成的循环是空的。

从文档中不清楚这在最新版本中是否仍然正确。该手册提到“历史上”,但没有说明原因。如果您使用有关您的确切平台和编译器的信息更新您的问题,也许可以给出更好的答案。

【讨论】:

  • 你能具体说明“被优化器清空”是什么意思吗?你的意思是启用优化?
  • @Banex 除非启用优化,否则优化器不会处于活动状态,所以是的,启用优化。 :) 如果一个循环包含可以移出循环或完全删除的代码,因为编译器可以证明它从未被使用过,那么 gcc 可以完全删除循环。
  • 非常好。如果他输入“int i = 0;”,OP 的代码会运行多快?在循环内部?
  • 一个示例可能会有所帮助,例如int main(void) { int i, k; for (i = 0; i &lt; 1000000; ++i) ++k; puts("Done"); }k 的值从未使用过,因此可以优化 k。因为k 的增量永远不会发生,循环甚至不需要增加k,因为这是它唯一的工作。结果,编译器可以优化循环。 i 的值现在也未使用,因此也可以对其进行优化,留下 int main(void){puts("Done");} 作为优化后的程序。这发生在我在 GCC 5.2.0 中编译为 64 位 Windows 代码。
  • @ChronoKitsune 更进一步,假设您返回 k 而不是忽略它。在这种特定情况下,编译器可以仍然删除循环,并简单地将程序优化为int main(void){return 0xf4240;}
【解决方案3】:

C 或 C++ 可执行文件没有最短执行时间,因为执行时间取决于许多平台特定问题,例如:

  1. 处理器时钟频率。
  2. 每条指令的时钟周期。
  3. 内部处理器执行优化。
  4. 中断。
  5. 处理器指令集/功能。

有些处理器支持乘法,有些则不支持。与具有乘法指令的进程相比,不支持乘法的处理器执行程序所需的时间更长。与浮点数相同。

处理器的内部运行速度会有所不同。有一个通用的时间测量单位,称为“时钟周期”。大多数处理器供应商都以时钟周期为单位指定指令的持续时间。由于内部支持(例如缓存管理),这种测量可能很困难。

某些处理器具有可以优化指令执行或指令模式的逻辑。一种优化是分支预测

许多平台都有中断。例如,可能有一个“系统滴答”中断,它允许操作系统知道何时将执行切换到另一个程序。有些不是那么周期性的,例如当 I/O 发生时。当程序被中断时,不能保证最短的执行时间。

规定最小执行时间会严重破坏 C 和 C++ 语言的可移植性。一些平台希望以比最短时间更快的速度执行代码。其他平台可能无法实现最短执行时间(但它们可以从 C 等高级语言中受益)。

另外,如何测量时间?

最小执行时间是否适用于延迟循环或轮询?

【讨论】:

  • 除此之外,许多操作系统上的时钟频率也会有所不同,具体取决于负载。即使是相对于此的术语“时钟周期”。
【解决方案4】:

不,不保证:(引用自N1570, 5.1.2.3 Program execution

1 本国际标准中的语义描述描述 抽象机器的行为,其中优化问题 无关紧要。

无论如何,C 标准只指定程序在抽象机器上执行时的行为,抽象机器可以有无限的内存和/或 CPU。

【讨论】:

  • 作为推论,如果“编译器”只是将每个输入转换为无限循环,它将是严格符合的;争辩说它正在做正确的行为,只是非常缓慢
  • @M.M 我认为如果输入终止,输出也必须终止。 “非常缓慢”和“从不”是有区别的。
  • @M.M 不,这样的编译器不会严格遵守。不要将无法证明任何给定程序将终止(停止问题)与无法对任何特定程序执行此操作相混淆。如果可以证明(例如)编译器对Hello, world! 的翻译输出将永远不会打印Hello, world!,则编译器不符合要求,我们可以尽可能彻底和巧妙地证明(而编译器是大概在混淆非终止的能力方面受到限制)。
  • @JeroenMostert find_collatz_counterexample(); printf("Hello world");
  • @mbrig:Hello, world! 我指的是规范程序,当然,不是我们自己无法判断它是否终止的任意程序。我们无法使用这样的程序来得出关于编译器的任何信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-06-22
  • 2010-10-27
  • 2013-07-06
  • 2018-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多