23

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

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

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

我想像别名这样的东西使得推导出一些优化变得困难甚至不可能,那么在我们开始冒险进入足够智能的编译器领域之前,界限在哪里?

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

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

4

8 回答 8

9

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

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

于 2013-04-12T11:48:18.077 回答
5

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

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

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

于 2013-04-12T11:49:00.000 回答
5

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

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

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

至于在哪里画线……它会随着时间而变化。当我第一次开始用 C 编程时,register做出了显着的改变,并被广泛使用。今天不。我想随着时间的推移,同样的情况可能会发生,inline或者restrict——一些实验性编译器已经非常接近了inline

于 2013-04-12T11:56:11.600 回答
5

这是一个诱饵问题,但无论如何我都会深入研究。

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

我认为inline和 一样糟糕register

一般来说,在修改之前先测量。如果你发现你的代码性能太差了,你想使用registeror inline,深呼吸,退后一步,先寻找更好的算法。

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

于 2013-04-12T11:58:17.747 回答
2

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

通常,您不应该猜测优化关键字或其他优化技术是否良好。人们必须对计算机的工作方式有相当多的了解,包括您所针对的特定平台以及编译器的工作方式。

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

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

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

于 2013-04-12T13:47:11.970 回答
2

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

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

对于这些情况,我发现 、 和 等优化提示register非常inline有用#pragma unroll

于 2013-04-12T16:58:46.840 回答
2

区别如下:

  • 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)?编译器必须推断出这一点,dst并且src不会以任何方式别名,并且它们指向的区域是独立的。size可能取决于用户输入或运行时行为或其他元素,这使得分析实际上不可能 - 与停止问题类似的问题 - 通常我们无法在不运行它的情况下分析所有内容。或者它可能是 C 库的一部分(我假设是共享库)并且由程序调用,因此所有调用站点在编译时甚至都不知道。如果没有这样的分析,程序将在优化时表现出不同的行为。另一方面,程序员可以通过了解(甚至更高级别的)设计而不是自下而上的分析来确保它们是不同的对象。

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

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

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

于 2013-04-12T19:54:29.543 回答
0

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

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

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

于 2013-04-14T18:57:44.800 回答