全局变量意味着更快的代码?

时间:2010-10-17 09:23:34

标签: c++ c optimization

我最近在1996年写的article on game programming中读到,使用全局变量比传递参数更快。

这是真的,如果是的话,今天仍然如此吗?

14 个答案:

答案 0 :(得分:42)

简短回答 - 不,优秀的程序员通过了解和使用适当的工具来使代码变得更快,然后以有条不紊的方式优化他们的代码不符合他们的要求。

更长的答案 - 这篇文章,在我看来并不是特别精心编写的,在任何情况下都不是关于程序加速的一般建议,而是'15种做更快的blits的方法'。无论你如何看待这篇文章的优点,将其推断为一般情况都是缺少作者的观点。

如果我正在寻找性能建议,我会在一篇不识别或显示单个具体代码更改的文章中放置零信任,以支持示例代码中的断言,并且不建议测量代码可能是个好主意。如果您不打算如何更好地编写代码,为什么要包含它呢?

有些建议已经过时了 - 很久以前,FAR指针在电脑上停止了问题。

一位认真的游戏开发人员(或任何其他专业程序员)会对这样的建议大笑:

  

你可以取出断言   完全,或者你可以添加一个   编译最终版本时#define NDEBUG

我建议你,如果你真的想评估这15个技巧中的任何一个的优点,并且因为这篇文章已经14年了,那就是用现代编译器编译代码(Visual C ++ 10说)并尝试识别使用全局变量(或任何其他提示)的任何区域将使其更快。

[只是在开玩笑 - 我真正的建议是完全忽略这篇文章,并在你的工作中遇到无法解决的问题时,在Stack Overflow上询问具体的性能问题。这样,您获得的答案将经过同行评审,并得到示例代码或良好的外部证据以及当前的支持。]

答案 1 :(得分:26)

当您从参数切换到全局变量时,可能会发生以下三种情况之一:

  • 运行得更快
  • 它运行相同的
  • 运行速度慢

你必须衡量性能,看看在一个非平凡的具体案例中哪些更快。这在1996年是真的,今天是真的,明天也是如此。

将性能搁置一会儿,大型项目中的全局变量引入了依赖关系,几乎总是使维护和测试变得更加困难。

当出于性能原因试图找到全局变量的合法用法时,我非常同意examples in Preet's answer:微控制器程序或设备驱动程序中经常需要的变量。极端情况是处理器寄存器,专门用于全局变量。

在推理全局变量与参数传递的性能时,编译器实现它们的方式是相关的。全局变量通常存储在固定位置。有时,编译器会生成直接寻址以访问全局变量。但有时,编译器会再使用一个间接,并为globals使用一种符号表。 IIRC gcc for AIX在15年前做到了这一点。在这种环境中,小型的全局变量总是慢于本地和参数传递。

另一方面,编译器可以通过将参数传递到堆栈中来传递参数,方法是将它们传递给寄存器或两者的混合。

答案 2 :(得分:22)

每个人都已经就这个平台和程序特定,需要实际测量时间等给出了相应的警告答案。所以,所有已经说过的,让我直接回答你的问题,针对x86上游戏编程的具体情况和PowerPC。

1996年,在某些情况下,将参数推送到堆栈上需要额外的指令,并可能导致英特尔CPU管道内的短暂停顿。在这些情况下,完全避免参数传递和从文字地址读取数据可能会有非常小的加速。

在x86或大多数游戏机上使用的PowerPC上都不再如此。使用全局变量通常比传递参数更慢有两个原因:

  • 现在更好地实现了参数传递。现代CPU在寄存器中传递其参数,因此从函数的参数列表中读取值比内存加载操作更快。 x86使用寄存器阴影和存储转发,所以看起来像将数据混洗到堆栈上并返回实际上可以是一个简单的寄存器移动。
  • Data cache latency far outweighs CPU clock speed in most performance considerations。大量使用的堆栈几乎总是在缓存中。从任意全局地址加载可能导致高速缓存未命中,这是一个巨大的损失,因为内存控制器必须从主RAM获取数据。 (“Huge”这里是600次或更多次。)

答案 3 :(得分:12)

你是什么意思,“更快”?

我知道一个事实,用全局变量理解一个程序比没有变换程序花费我更多的时间。

如果程序员花费的额外时间少于用户在使用全局变量运行程序时所获得的时间,那么我会说使用global更快。

但考虑到该计划将由10人每天一次运行2年。而且没有全局变量需要2.84632秒而全局变量需要2.84217秒(增加0.00415秒)。这比 TOTAL 运行时少727秒。在程序员时间方面,获得10分钟的运行时间并不值得引入全局。

答案 4 :(得分:11)

在某种程度上任何避免处理器指令(即更短代码)的代码都会更快。但是要快多少? 不是很好!另请注意,编译器优化策略可能会导致代码更小。

目前,这只是对非常特定的应用程序的优化,通常用于超时间关键驱动程序或微控制代码。

答案 5 :(得分:7)

撇开可维护性和正确性的问题,基本上有两个因素可以控制全局与参数的性能。

当你创建全局时,你会避免复制。那稍快一些。当您按值传递参数时,必须将其复制,以便函数可以在其本地副本上工作,而不会损坏调用者的数据副本。至少在理论上。如果一些现代优化器确定您的代码表现良好,那么它们会做很棘手的事情。函数可能会自动内联,编译器可能会注意到函数对参数没有任何作用,只是优化掉任何复制。

当你创建一个全局时,你就是在缓存中。如果您的所有变量都包含在您的函数中,并且包含一些参数,那么数据将集中在一个位置。一些变量将在寄存器中,一些变量可能会立即存在于缓存中,因为它们彼此正确“相邻”。使用大量全局变量基本上是缓存的病态行为。无法保证相同功能将使用各种全局变量。位置与使用没有明显的相关性。也许你有一个足够小的工作集,它在任何地方都没有区别,而且它们都会在缓存中结束。

所有这些只是加上我上面的海报所提出的观点:

  

从参数切换到   全局变量,三件事之一   可能发生:

* it runs faster
* it runs the same
* it runs slower
     

您必须衡量效果   看看什么是非平凡的更快   具体案例。这在1996年是如此,   今天是真的,明天也是如此。

根据您的确切编译器的具体行为以及用于运行代码的硬件的精确详细信息,在某些情况下,全局变量可能会获得非常轻微的性能提升。这种可能性值得在一些运行速度太慢的代码上尝试。可能不值得投入,因为明天你的实验答案可能会改变。因此,正确的答案几乎总是采用“正确”的设计模式,避免更丑陋的设计。在故意尝试破坏您的项目之前,寻找更好的算法,更高效的数据结构等。从长远来看,收益会好得多。

除了开发时间与用户时间参数之外,我还将添加开发时间与摩尔时间参数。如果你认为摩尔定律每年会使计算机的速度再次降低一半,那么为了简单的整数,我们可以假设每周进展稳定1%。如果您正在考虑可能会改善1%这样的事情的微优化,并且它会使项目因复杂化而增加一周,那么只需休息一周就会对用户的平均运行时间产生同样的影响。

答案 6 :(得分:3)

也许是微优化,并且可能会被编译器可能产生的优化所消除,而无需借助这些实践。事实上,全局变量的使用甚至可能会抑制一些编译器优化。可靠和可维护的代码通常具有更大的价值,全局变量不利于此。

使用全局变换来替换函数参数会使所有这些函数不可重入,如果使用多线程可能会出现问题 - 这在1996年的游戏开发中并不常见,但随着多核处理器的出现更为常见。它也排除了递归,虽然这可能不是一个问题,因为递归有其自身的问题。

在任何重要的代码体系中,算法和数据结构的更高级优化可能会有更多的里程数。此外,除了避免参数传递的全局变量之外,还有其他选项可供选择,尤其是C ++类成员变量。

如果代码中习惯性使用全局变量会对其性能产生可测量或有用的差异,我会首先质疑设计。

有关全局变量中固有问题的讨论以及避免它们的一些方法,请参阅Jack Gannsle撰写的A Pox on Globals。本文涉及嵌入式系统开发,但通常适用;它只是一些嵌入式系统开发人员认为他们有充分的理由使用全局变量,可能是因为在游戏开发中用来证明它的所有相同的错误理由。

答案 7 :(得分:2)

好吧,如果您正在考虑使用全局参数而不是参数传递,那么这可能意味着您必须将一长串方法/函数传递给该参数。在这种情况下,您真的会通过从参数切换到全局变量来节省CPU周期。

所以,那些说这取决于的人,我猜他们是完全错的。即使使用REGISTER参数传递,仍然会有更多的cpu周期和更多的开销,将参数推送到被调用者。

但是 - 我从来没有这样做过。 CPU现在更优越,有时可能会出现12Mhz 8086。如今,如果你不编写嵌入式或超级涡轮增压的性能代码,那么坚持使用代码看起来不错的代码,它不会破坏代码逻辑,并且可以实现模块化。

最后,将机器语言代码生成留给编译器 - 设计它的人最好知道他们的宝宝表现如何,并使你的代码运行得最好。

答案 8 :(得分:1)

一般情况下(但它可能在很大程度上取决于编译器和平台实现),传递参数意味着将它们写入堆栈,而全局变量则不需要它。

也就是说,全局变量可能意味着包括MMU或内存控制器中的页面刷新,而堆栈可能位于处理器可用的更快的内存中......

对不起,对于像这样的一般性问题没有好的答案,只需测量它(并尝试不同的场景)

答案 9 :(得分:1)

当我们拥有< 100mhz处理器时速度更快。现在处理器的速度提高了100倍,这个“问题”的重要性就低了100倍。那不是什么大不了的事,当你在汇编中做到并且没有(好的)优化器时这是一个大问题。

说那个用3mhz处理器编程的人。是的你读得对,64k还不够。

答案 10 :(得分:1)

我看到了许多理论上的答案,但没有针对您的情景提供实用建议。我猜测的是你有大量的参数可以通过许多函数调用传递下来,而你担心来自多个级别的调用帧和每个级别的许多参数的累积开销。否则你的担忧是完全没有根据的。

如果这是您的场景,您应该将所有参数放在“上下文”结构中,并将指针传递给该结构。这将确保数据的局部性,并使其不必在每次函数调用时传递多个参数(指针)。

以这种方式访问​​的参数比真正的函数参数稍微昂贵一些(你需要一个额外的寄存器来保存指向结构基础的指针,而不是用于为函数参数服务的帧指针),和普通的非PIC代码中的全局变量相比,单独(但可能没有考虑缓存效应)的访问成本更高。但是,如果您的代码使用position independent code在共享库/ DLL中,则访问由struct传递给struct的参数的成本比访问全局变量更便宜,并且与访问静态变量相同,因为GOT和GOT相对寻址。这是永远不会使用全局变量进行参数传递的另一个原因:如果您最终可能将代码放在共享库/ DLL中,任何可能的性能优势都会突然适得其反!

答案 11 :(得分:1)

和其他一切一样:是和否。没有一个答案,因为它取决于背景。

Counterpoints:

  • 想象一下在Itanium上编程,你有数百个寄存器。你可以将相当多的全局变量放入其中,这将比在C中实现全局变量的典型方式更快(一些静态地址(尽管如果它们是字长,它们可能只是将全局变量硬编码为指令))。即使全局变量在整个缓存中,寄存器仍然可能更快。

  • 在Java中,过度使用全局变量(静态)可能会降低性能,因为必须完成初始化锁定。如果有10个类想要访问某个静态类,那么它们都必须等待该类完成其静态字段的初始化,这可以在任何时候形成,直到永远。

无论如何,全局状态只是不好的做法,它会增加代码的复杂性。精心设计的代码自然足够快 99.9%的时间。似乎较新的语言正在一起消除全球状态。 E删除全局状态,因为它违反了他们的安全模型。 Haskell一起删除状态。 Haskell存在并且实现性能超过大多数其他语言的事实足以证明我永远不会再使用全局变量。

此外,在不久的将来,当我们都有数百个核心时,全球状态并没有真正帮助太多。

答案 12 :(得分:1)

在某些情况下可能仍然如此。 全局变量可能与指向变量的指针一样快,其指针仅存储在寄存器中/通过寄存器传递。所以,这是一个关于寄存器数量的问题,你可以使用。

要快速优化函数调用,您可以执行其他一些操作,使用全局变量hack可能会有更好的效果:

  • 将函数中局部变量的计数最小化为几个(显式)寄存器变量。
  • 最小化函数的参数计数,即使用指向结构的指针,而不是在相互调用的函数中使用相同的参数星座。
  • 使功能“裸露”,这意味着它根本不使用堆栈。
  • 使用“proper-tail-calls”(既不适用于java / -bytecode也不适用于java- / ecma-script)
  • 如果没有更好的方法,那么就像TABLES_NEXT_TO_CODE一样破解自己,它会在功能代码旁边找到你的全局变量。在函数式语言中,这是一个后端优化,它也使用函数指针作为数据指针;但只要您不使用函数式语言编程,您只需要在函数使用的那些变量旁边找到这些变量。然后,您只需要从函数中删除堆栈处理。如果你的编译器生成了处理堆栈的汇编程序代码,那么就没有必要这样做,你可以改用指针。

我发现了这个“gcc属性概述”: http://www.ohse.de/uwe/articles/gcc-attributes.html

我可以为google搜索这些标签: - 正确的尾调用(它主要与功能语言的命令后端相关) - TABLES_NEXT_TO_CODE(它主要与Haskell和LLVM相关)

答案 13 :(得分:0)

但是当你经常使用全局变量时,你会有'意大利面条代码'。