编译器优化的示例,可以在C ++代码上轻松完成,但不能在C代码上完成

时间:2012-04-09 21:19:30

标签: c++ c optimization compiler-optimization

这个问题讨论了在C中无法轻易实现的排序函数的优化: Performance of qsort vs std::sort?

与C ++相比,是否有更多的编译器优化示例在C中是不可能或至少难以实现的?

3 个答案:

答案 0 :(得分:8)

正如@sehe在评论中提到的那样。这比抽象更重要。换句话说,如果语言允许编码人员更好地表达 intent ,那么它可以发出以更优化的方式实现该意图的代码。

一个简单的例子是std::fill。当然对于基本类型,您可以使用memset,但是,假设它是一个32位unsigned long的数组。 std::fill 知道数组大小是32位的倍数。根据编译器的不同,它甚至可以假设数组也在32位边界上正确对齐。

所有这些结合可能允许编译器发出代码,一次设置32位值,没有运行时检查以确保它是有效的。如果我们很幸运,编译器将识别这一点,并用特别有效的特定于架构的代码版本替换它。

(实际上gcc和其他主流编译器实际上可以做的几乎任何可以被视为等同于memset的内容,包括std::fill)。

通常,memset的实现方式是对这些类型的事物进行运行时检查,以便选择最佳的代码路径。虽然这种差异可能是微不足道的,但我们的想法是我们更好地表达了“填充”具有特定值的数组的 intent ,因此编译器能够做出稍微更好的选择。

其他更复杂的语言功能可以很好地使用意图表达来获得更大的收益,但这是最简单的例子。

要明确,我的观点并不是std::fillmemset“更好”,而是c ++如何允许更好地表达意图的示例编译器,允许它在编译期间获得更多信息,从而使一些优化更容易实现。

答案 1 :(得分:3)

这取决于您对此处的优化的看法。如果你认为它纯粹是“std :: sort vs. qsort”,那么还有成千上万的其他类似的优化。使用C ++模板可以支持内联,在这种情况下,C中唯一合理的替代方法是使用指向函数的指针,几乎没有已知的编译器会内联被调用的代码。根据您的观点,这可以是单个优化,也可以是整个(开放式)系列。

另一种可能性是使用模板元编程将某些内容转换为编译时常量,通常必须在运行时使用C计算。理论上, 通常可以通过嵌入一​​个神奇的数字。这可以通过#define进入C,但可能失去上下文,灵活性或两者兼而有之(例如,在C ++中,您可以在编译时定义一个常量,从该输入执行任意计算,并生成编译时其余代码使用的常量。鉴于您可以在#define中执行更有限的计算,这种情况几乎不可能发生。

另一种可能性是函数重载和模板特化。它们是分开的,但给出相同的基本结果:使用专用于特定类型的代码。在C中,为了使你处理的函数数量保持合理,你经常会编写代码(例如)将所有整数转换为long,然后对其进行数学运算。模板,模板特化和重载使得使用代码保持较小类型的原始大小相对容易,这可以大大提高速度(特别是当它可以启用数学矢量化时)。

最后一个显而易见的可能性源于简单地提供了相当多的预构建数据结构和算法,并允许这些东西被打包以便相对容易,有效地重复使用。我怀疑我甚至可以使用我所知道的相对低效的数据结构和/或算法来计算我在C中编写代码的次数,仅仅是因为不值得花时间找到(或适应)更高效的数据结构和/或算法。手头的任务。是的,如果它确实成为主要的瓶颈,我就会找到更好的东西 - 但是做一些比较,在写入时速度加倍仍然相当普遍C ++。

然而,我应该补充一点,所有这些无疑是可能与C,至少在理论上是这样。如果你从语言复杂性理论和理论计算模型(例如图灵机)的角度来看待这一点,毫无疑问C和C ++是等价的。有足够的工作来编写每个函数的专用版本,理论上你可以用C语言完成所有这些相同的事情。

从你可以计划在实际项目中编写的代码的角度来看,故事变化很快 - 你可以做的事情的限制主要归结为你可以合理管理的东西,而不是像理论模型那样由语言表示的计算。在C语言中几乎完全理论化的优化级别不仅实用,而且在C ++中非常常规。

答案 2 :(得分:-1)

即使qsort vs std::sort示例无效。如果需要C实现,它可以在qsort中放置stdlib.h的内联版本,任何体面的C编译器都可以处理内联比较函数。通常没有做到这一点的原因是它大量膨胀并且具有可疑的性能优势 - C ++人们往往不关心......