【问题标题】:Are the Optimization Keywords in C and C++ Reasonable?C 和 C++ 中的优化关键字是否合理?
【发布时间】:2013-04-04 22:18:06
【问题描述】:

所以我们都听说过 don't-use-register 这一行,原因是试图优化编译器是徒劳的。

register,据我所知,实际上并没有说明任何关于 CPU 寄存器的内容,只是不能间接引用给定的变量。我会冒险猜测它通常被称为过时,因为编译器可以自动检测到缺少寻址,从而使此类优化变得透明。

但是,如果我们对这个论点持坚定态度,难道不能把它放在 C 中每个优化驱动的关键字上吗?比如我们为什么要用inline和C99的restrict

我想像别名这样的东西使得推导出一些优化困难甚至不可能,那么在我们开始冒险进入Sufficiently Smart Compiler 领域之前划定的界限在哪里?

应该在 C 和 C++ 中在舀入编译器优化信息和假设它知道自己在做什么之间划清界限?

编辑:Jens Gustedt 指出我将 C 和 C++ 混为一谈是不对的,因为其中两个关键字具有语义差异,而标准 C++ 中不存在一个。我在 C++ 中有一个关于 register 的很好的链接,如果我找到它,我会添加它...

【问题讨论】:

  • 我对 C 了解不多,但在 C++ 中,inline 被用来规避 ODR。
  • “应该在哪里画线” - 我认为它应该总是被基准测试分解。选择产生更好结果的那个。
  • 你真的应该区分C和C++,在这里,它们与你提到的所有三个关键字都不一样:inlineregister具有不同的语义,restrict甚至不存在在 C++ 中。
  • @JensGustedt 谢谢,我会将其添加为编辑。
  • register 在 C++11 中被弃用的事实相当有说服力。现代优化编译器没有任何好处。然而,GCC 在非标准 extension 中使用它

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


【解决方案1】:

我同意registerinline 在这方面有些相似。如果编译器在编译调用站点时可以看到被调用者的主体,那么它应该能够对内联做出正确的决定。在 C 和 C++ 中使用 inline 关键字更多地与使函数体可见的机制有关。

restrict,然而,是不同的。编译函数时,编译器不知道调用站点将是什么。能够假设没有别名可以实现原本不可能的优化。

【讨论】:

  • 我认为registerrestrict 的相似之处在于它们都告诉编译器您允许它假设什么。 register 更好,因为它还允许编译器强制执行。但属于同一类。哦,等一下……这也适用于inline
  • 今天的编译器通常可以看到函数的所有调用点,至少如果您使用最大优化算法的话。然而,确实,受register 影响的局部性要小得多,更重要的是,优化寄存器分配的算法现在已经广为人知并被普遍使用,而与inline 或相关的优化并非如此。 restrict.
  • @JamesKanze:出于兴趣,编译器能够查看所有调用站点的机制是什么?假设我构建了一个包含名为foo() 的函数的库,并将.a 文件提供给您。您编写了一个名为bar() 的函数,它调用foo()。编译foo()时,编译器怎么知道bar()
  • @NPE 我认为通常的解决方案是让编译器生成带注释的字节码,类似于 Java 编译器生成的内容,然后仅在链接阶段将其编译为机器码。对于 g++,请参见 -lto,对于 VC++,请参见 /GL
  • @NPE 编译器能否在 unique_ptr 上假设 restrict 语义?
【解决方案2】:

inline 用于在标头中实现非模板化函数然后从多个编译单元包含它的场景。

这确保编译器应该只创建一个函数实例,就像它是内联的一样,因此您不会收到多重定义符号的链接错误。然而,它并不要求编译器实际内联它。

我认为有些 GNU 标志是 force-inline 或类似的,但那是一种语言扩展。

【讨论】:

  • inline 可以用于制作仅库头文件。但是,仅标题通常是一个严重的缺点。它允许人们在您从未听说过的机器上使用您的库,更不用说对其进行测试了。
  • gnu强制内联的方式其实是函数属性“always_inline”。
【解决方案3】:

register 甚至没有说你不能引用 间接变量(至少在 C++ 中)。它说,在 原来的C,但已被删除。

试图优化编译器是否是愚蠢的差事 取决于优化。例如,没有多少编译器, 会将sin(x) * sin(x) + cos(x) * cos(x) 转换为1

今天,大多数编译器都忽略了register,也没有人使用它, 因为编译器在寄存器分配方面已经足够好 比register 做得更好。实际上, 尊重register 通常会使生成的代码 慢点。 inlinerestrict 的情况不是:在 在这两种情况下,至少在理论上都存在技术, 这可能导致编译器比你做得更好 能够。然而,这样的技术并不普遍,而且(到目前为止 据我所知,至少),编译时间开销非常高, 在某些情况下,编译时间呈指数增长 程序的大小(这使它们或多或少无法使用 在大多数真实程序上——编译时间以 年真的不能接受)。

至于在哪里画线……它会随着时间而变化。什么时候 我第一次开始用 C 编程,register 取得了重大进展 区别,被广泛使用。今天不。我想在 时间,inlinerestrict 也可能发生同样的情况——有些 实验编译器已经与inline 非常接近。

【讨论】:

  • 当您开始使用 C 进行编程时,您可能正在编写非常接近系统的代码,并且是在单进程环境中编写的,您的程序是唯一运行在 DOS PC 上的东西。
  • +1 但我认为我们需要的是具有附加语义限制的类型,以表达“我想要标准功能,如果这改变了语义就不要优化”。虽然主要用于 g++。一种 g++ 不能破坏的特殊浮点类型,并且数字限制信息总是可靠的,以及无论-fwrapv 与否都不能破坏的特殊整数类型(假设没有换行)。跨度>
  • @CashCow 当我开始用 C 编程时,编译器只有 64 KB 的 RAM 可以玩,而且它会溢出到的磁盘是一张软盘。今天可能需要 10 毫秒的优化策略可能需要几分钟。并非所有人都为人所知:Sethi-Ullman 是最先进的,并且注册着色刚刚出版。
  • 关于register关键字,取决于编译器。如果您不启用任何优化,GCC 将兑现它 - 实际上这可以使代码更快。
  • 作为旁注sin(x) * sin(x) + cos(x) * cos(x) 不能优化为1,因为实数的恒等式适用于 IEEE 754 浮点数。例如sin(nan) * sin(nan) + cos(nan) * cos(nan) = nan(与inf 类似,结果为nan)。同样,原始公式可能存在舍入错误(对于 32 位浮点数,(sin(x) * sin(x) + cos(x) * cos(x)) - 1 的结果为-5.9604645e-8)。
【解决方案4】:

这是一个引人入胜的问题,但无论如何我都会深入研究。

编译器比普通程序员更擅长优化。有一段时间我在 25MHz 68030 上编程,我从使用 register 中获得了一些优势,因为编译器的优化器太差了。但那是 1990 年的事了。

我认为inlineregister 一样糟糕。

一般来说,在修改之前先测量。如果你发现你的代码执行得很差,想要使用registerinline,请深呼吸,退后一步,先寻找更好的算法。

最近(即过去 5 年),我检查了代码库并删除了大量 inline 函数,但性能没有明显变化。然而,代码大小总是受益于删除inline 方法。对于现代标准 x86 风格的怪物多核奇迹来说,这不是什么大问题,但如果你在嵌入式领域工作,这确实很重要。

【讨论】:

  • 在修改之前先测量(然后再测量)是给定的。尽管如此,对于大多数当前的编译器,明智地使用inline 可以做出显着的改进(一旦您确定需要改进),而且只需很少的努力。我见过几个案例,restrict 可以在一个小的关键功能(可能需要长达 20 分钟的执行时间)上带来数量级的改进。
  • 关于你的最后一段——似乎有滥用inline的倾向。过早优化就是过早优化,程序的原始版本不应该包含任何inline函数。但这并不意味着当需要优化时,inline 是一个糟糕的工具。它是您可以使用的最便宜的工具之一(就人力和代码可读性的成本而言)。关键是只在必要时使用它。
  • inline 语义在 C99 中很有用,因为函数的实现可以在翻译单元中实例化。如果编译器决定内联代码,它就不必在多个翻译单元中实现静态范围的实现。这对函数指针也很有用。
  • 内联的意义不是真的不强制编译器做任何事情吗?它只是一个提示,不像 register 那样,因此,就像 register 关键字一样,可能变得无用。关于大小的点,尝试使用 -Osize,而不是仅仅删除所有的内联关键字
【解决方案5】:

这是一个移动的目标,因为编译器技术正在改进。 (嗯,有时它的变化多于改进,但这与使您的优化尝试变得毫无意义的效果相同,或者更糟。)

一般来说,您不应该猜测优化关键字或其他优化技术是否好用。必须对计算机的工作原理有相当多的了解,包括您所针对的特定平台以及编译器的工作原理。

所以关于使用各种优化技术的规则是问我是否知道编译器不会在这里做得最好?我是否愿意为此承诺一段时间——在使用这段代码时编译器是否会保持稳定,当编译器改变这种情况时我是否愿意重写代码?通常,您必须是一位经验丰富且知识渊博的软件工程师,才能知道何时可以比编译器做得更好。如果您可以与编译器开发人员交谈,这也会有所帮助。

这意味着人们无法在这里给你一个有明确指导方针的答案。这取决于您使用的是什么编译器、您的项目是什么、您的资源是什么、您的目标是什么等等。

尽管有些人说不要试图超越编译器的优化,但在软件工程的各个领域,人们都比编译器做得更好,而且为此付出代价是值得的。

【讨论】:

    【解决方案6】:

    区别如下:

    • register 是非常局部的优化(即在一个函数内)。寄存器分配是一个相对解决的问题,无论是通过更智能的编译器还是通过更多数量的寄存器(主要是前者,但说 x86-64 的寄存器比 x86 多,并且两者的数量都比 8 位处理器大)
    • inline 更难,因为它是过程间优化。但是,由于它涉及相对较小的递归深度和少量的过程(如果内联过程太大,则没有内联的意义),它可以安全地留给编译器。
    • restrict 更难。要完全了解这两个指针没有别名,您需要分析整个程序(包括库、系统、插件等) - 甚至会遇到问题。但是,对于程序员来说,信息更清楚,并且它是规范的一部分。

    考虑非常简单的代码:

    void my_memcpy(void *dst, const void *src, size_t size) {
        for (size_t i = 0; i < size; i++) {
            ((char *)dst)[i] = ((const char *)str)[i];
        }
    }
    

    提高这段代码的效率有什么好处吗?是的 - memcpy 往往非常有用(比如用于复制 GC)。这段代码可以向量化吗(这里 - 用文字移动 - 说 128b 而不是 8b)?编译器必须推断出dstsrc 不会以任何方式产生别名,并且它们指向的区域是独立的。 size 可能取决于用户输入或运行时行为或其他元素,这使得分析实际上不可能 - 与停止问题类似的问题 - 通常我们无法在不运行它的情况下分析所有内容。或者它可能是 C 库的一部分(我假设是共享库)并且由程序调用,因此所有调用站点在编译时甚至都不知道。如果没有这样的分析,程序将在优化时表现出不同的行为。另一方面,程序员可以通过了解(甚至更高级别的)设计而不需要自下而上的分析来确保它们是不同的对象。

    restrict 也可以是文档的一部分,因为可能是 程序员 以无法处理 2 个别名指针的方式编写了该过程。例如,如果我们想从别名位置复制内存,上面的代码是不正确的。

    总结一下——足够智能的编译器能够推断出restrict(除非我们转向理解代码含义的编译器)不知道整个程序。即使那样,它也接近于不可判定性。然而,对于 local 优化,编译器已经足够聪明了。我猜想,具有整个程序分析的 Sufficiently Smart Compiler 将能够在许多有趣的情况下进行推断。

    PS。本地我的意思是单一功能。所以局部优化不能假设任何关于参数、全局变量等的东西。

    【讨论】:

      【解决方案7】:

      没有提到的一件事是,许多非 x86 编译器在优化方面不如 gcc 和其他“现代” C 编译器。

      例如,compilers for PIC 在优化方面绝对糟糕。此外,the optimizer for ciccCUDA 编译器)虽然好得多,但似乎仍然错过了很多相当简单的优化。

      对于这些情况,我发现像 registerinline#pragma unroll 这样的优化提示非常有用。

      【讨论】:

        【解决方案8】:

        根据我在更多地参与 C/C++ 的日子里所看到的,这些只是直接给编译器的命令。编译器可能会尝试内联一个函数,即使它没有被直接命令这样做。这实际上取决于编译器,甚至可能引发一些交叉编译器问题。例如,Visual Studio 提供了不同级别的优化,对应于编译器的不同智能级别。我已经读过所有的类函数都是隐式的 inline 给编译器一个提示来最小化函数调用开销。在任何情况下,这些指令在您使用不太智能的编译器时非常有用,而在智能情况下,编译器进行一些优化可能非常明显。

        另外,请确保这些关键字是安全的。某些编译器优化可能不适用于某些库,例如 OpenGL(我自己也看到过)。因此,在您觉得编译器优化可能有害的情况下,您可以使用这些关键字来确保它按照您想要的方式完成。

        现在的 g++ 等编译器对代码的优化非常好。您不妨在其他地方搜索优化,可能在您使用的方法和算法中,或者通过使用 TBB 或 CUDA 使您的代码并行。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-02-24
          • 1970-01-01
          • 2011-02-13
          • 1970-01-01
          • 2013-08-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多