全局variables是否代表更快的代码?

我最近在一篇关于 1996年写的游戏编程的文章中读到,使用全局variables比传递参数要快。

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

简短的回答 – 不,好的程序员通过知道并使用合适的工具来加快代码的执行速度,然后以有条不紊的方式优化代码,使其代码不符合要求。

较长的回答 – 这篇文章在我看来并不是特别写得好,在任何情况下都不是关于程序加速的一般性build议,而是“15种更快的方法”。 无论你如何看待这篇文章的优点,把这个推断到一般情况下都是错失了作者的观点。

如果我在寻找性能方面的build议,我会在一篇没有标识或显示一个具体的代码变化的文章中放置零信任来支持示例代码中的断言,并且没有build议测量代码可能是一个好主意。 如果你不打算如何使代码更好,为什么包括它?

有些build议已经过时了很久,FAR的指针在很久以前就不再是个人电脑上的问题了。

一个认真的游戏开发者(或者其他专业程序员)会对这样的build议感到好笑:

您可以完全取出断言,也可以在编译最终版本时添加#define NDEBUG

我对你的build议,如果你真的想评估这15个技巧中的任何一个的优点,并且自从这篇文章是14岁以后,将会用现代编译器(Visual C ++ 10说)来编译代码,并尝试识别使用全局variables(或任何其他提示)的区域会使其更快。

[只是开玩笑 – 我真正的build议是完全忽略这篇文章,并问Stack Overflow的具体性能问题,因为你打你的工作,你不能解决的问题。 这样你得到的答案将被同行评审,由示例代码或良好的外部证据和当前支持。

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

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

你将不得不测量性能来看看在一个不平凡的具体情况下速度如何。 这在1996年是真的,今天是真的,明天是真的。

暂时抛开这个性能,一个大型项目中的全局variables引入了依赖性,这种依赖几乎总是使得维护和testing变得更加困难。

为了今天的性能原因,我试图find全局variables的合法用法,我非常同意Preet的答案中的例子 :在微控制器程序或设备驱动程序中经常需要variables。 极端情况是专门用于全局variables的处理器寄存器。

当推导全局variables与parameter passing的性能时,编译器实现它们的方式是相关的。 全局variables通常存储在固定位置。 有时编译器会生成直接寻址来访问全局variables。 然而,有时候,编译器会使用一种更为间接的方法,并使用一种符号表来表示全局variables。 IIRC gcc for AIX在15年前做了这个。 在这种环境下,小类的全局variables总是比本地variables和parameter passing慢。

另一方面,编译器可以通过将参数传入寄存器或将它们混合在一起来传递参数。

大家已经给出了关于这个平台和程序的具体的解答,需要实际测量时间等。所以,就这一切说,让我直接回答你的问题在x86和PowerPC上的游戏编程的具体情况。

在1996年,在某些情况下,将参数压入堆栈需要额外的指令,并可能导致Intel CPUpipe道短暂停顿。 在那些情况下,从完全避免parameter passing和从文字地址读取数据可能会有很小的加速。

这在x86或者大多数游戏机所使用的PowerPC上都不是这样。 使用全局variables通常比传递参数要 ,原因有二:

  • 现在parameter passing更好。 现代CPU将它们的parameter passing到寄存器中,所以从函数的参数列表中读取一个值比存储器加载操作要快。 x86使用寄存器映射和存储转发,所以看起来像将数据移动到堆栈上并返回,实际上可以是一个简单的寄存器移动。
  • 在大多数性能考虑中,数据caching延迟远远超过CPU时钟速度 。 被大量使用的堆栈几乎总是在caching中。 从任意全局地址加载可能导致caching未命中,这是一个巨大的代价,因为存储器控制器必须从主RAM中取出数据。 (这里的“巨大”是600个周期以上。)

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

我知道一个事实,即用一个全局variables来理解一个程序比没有一个要花更多的时间。

如果程序员花费的额外时间less于用户使用全局程序运行程序所获得的时间,那么我认为使用全局更快。

但是考虑到这个节目将由10人每天一次运行两年。 而无需全局variables需要2.84632秒,而全局变更需要2.84217秒(增加0.00415秒)。 这比TOTAL运行时间less了727秒。 运行时间达到10分钟是不值得引入一个全球性的程序员时间。

在某种程度上,避免处理器指令(即较短的代码)的任何代码将会更快 。 但是速度要快多less? 不是特别的! 另外请注意,编译器优化策略无论如何都可能导致较小的代码。

这些日子,这只是对通常在超时间关键驱动程序或微控制代码中的特定应用程序的优化。

撇开可维护性和正确性的问题,基本上有两个因素来pipe理全局对参数的性能。

当你成为一个全球性的时候你会避免一个副本 这稍微快一点。 当按值传递参数时,必须将其复制,以便函数可以在本地副本上工作,而不会损坏调用者的数据副本。 至less在理论上。 一些现代优化器如果确定你的代码performance良好,就会做相当棘手的事情。 一个函数可能会自动内联,编译器可能会注意到这个函数对参数没有任何作用,只是优化了任何复制。

当你创build一个全局的时候,你正在对caching说谎。 当你的函数中包含了所有的variables以及一些参数时,数据往往都集中在一个地方。 一些variables将在寄存器中,有些可能会立即被caching,因为它们彼此相邻。 使用大量全局variables基本上是caching的病态行为。 不能保证各种全局variables将被相同的function使用。 位置与使用情况没有明显的相关性。 也许你有一个足够小的工作集,它在任何地方都没有什么区别,而且都在caching中。

所有这一切只是加在我上面的海报上:

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

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

你将不得不测量性能来看看在一个不平凡的具体情况下速度如何。 这在1996年是真的,今天是真的,明天是真的。

根据您的确切编译器的具体行为以及您用于运行代码的硬件的确切细节,在某些情况下,全局variables可能会是一个非常轻微的performance。 这个可能性可能值得在一些运行速度过慢的代码上进行尝试。 这可能不值得奉献,因为你的实验的答案明天可能会改变。 所以,正确的答案几乎总是与“正确”的devise模式,避免丑陋的devise。 寻找更好的algorithm,更有效的数据结构等等,然后才有意地试图对你的项目进行表面化。 从长远来看,收益会更好。

而且,除了开发时间和用户时间的争论之外,我还会把开发时间与摩尔的时间论证相提并论。 如果你认为摩尔定律每年会使电脑的速度再快一点,那么为了简单的一个数字,我们可以假设每周进步速度稳定在1%。 如果您正在寻求一种可能改善1%的微观优化问题,并且将会使项目复杂化一周,那么仅仅closures一周就会对用户的平均运行时间产生相同的影响。

也许是一个微型优化,可能会被你的编译器在不采取这种做法的情况下产生的优化消除掉。 事实上,使用全局variables甚至可能会抑制一些编译器的优化。 可靠和可维护的代码通常具有更大的价值,全局性不利于此。

使用全局variablesreplace函数参数会使所有这些函数不可重入,如果使用multithreading,这可能会成为一个问题 – 这在1996年游戏开发中并不常见,但在多核处理器的出现中更为常见。 它也排除了recursion,尽pipe这可能不是一个问题,因为recursion有它自己的问题。

在任何重要的代码体系中,algorithm和数据结构的更高级优化可能会有更多的里程。 此外,除了避免parameter passing的全局variables之外,还有一些选项是可以打开的,尤其是C ++类成员variables。

如果代码中习惯使用全局variables会使其性能产生可衡量的或有用的差异,那么我首先会问这个devise。

有关全局variables固有的问题的讨论以及避免它们的一些方法,请参阅Jack Gannsle的“ 全球通病毒”( A Pox on Globals) 。 这篇文章涉及到embedded式系统的开发,但普遍适用; 只是一些embedded式系统开发人员认为他们有充分的理由使用全局variables,可能是为了在游戏开发中使用所有错误的理由。

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

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

但是 – 我从来不这样做。 CPU现在是优越的,有时候有12Mhz 8086可能是问题所在。 如今,如果您不写embedded式或超级涡轮增压的性能代码,那么坚持代码看起来不错的代码,不会破坏代码逻辑,并且是模块化的。

最后,将机器语言代码生成留给编译器 – devise它的人最好知道他们的孩子如何执行,并使代码运行得最好。

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

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

对不起,没有好的答案这样的一般问题,只是测量它(也尝试不同的场景)

当我们有<100mhz的处理器时速度更快。 现在处理器速度提高了100倍,这个“问题”的重要性就降低了100倍。 这不是什么大问题,当你在组装时没有(优秀)优化器,这是一个大问题。

那个用3mhz处理器编程的人说。 是的,你读了正确的,64K是不够的。

我看到很多理论上的答案,但对于你的情况没有实际的build议。 我猜测你有大量的parameter passing给一些函数调用,而且你担心来自不同级别的调用帧和许多参数的累积开销。 否则你的担心是完全没有根据的。

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

以这种方式访问​​的参数比真正的函数参数要贵一些(你需要一个额外的寄存器来保存指向结构底部的指针,而不是用函数参数来实现这个目的的帧指针),并且单独地但是可能不包含高速caching效应)比正常的非PIC代码中的全局variables更为昂贵。 但是,如果代码位于独立于代码的共享库/ DLL中,则由于GOT和GOT相对寻址,访问由结构指针传递的参数的成本比访问全局variables和访问静态variables相同。 这是从不使用全局variables进行parameter passing的另一个原因:如果您最终可能将您的代码放在共享库/ DLL中,任何可能的性能优势将突然反弹!

像其他的一样:是的,不。 没有一个答案,因为它取决于上下文。

Counterpoints:

  • 想象一下,在有数百个寄存器的Itanium上进行编程。 你可以把相当多的全局variables放入这些全局variables中,这将比全局variablesC(一些静态地址(如果它们是字长的话,它们可能只是将全局variables指令硬编码))中实现的全局变得更快。 即使全局caching,寄存器仍可能更快。

  • 在Java中,过度使用全局variables(静态)可能会降低性能,因为必须完成初始化locking。 如果10个类想要访问某个静态类,那么他们都必须等待该类完成初始化其静态字段,这可以在任何地方形成永久的forms。

无论如何,全局状态只是不好的做法,它提高了代码的复杂性。 精心devise的代码在99.9%的时间内自然足够快 。 好像新的语言一起去除了全局状态。 E删除全局状态,因为它违反了他们的安全模式。 Haskell一起移除状态。 Haskell存在并且具有超过大多数其他语言的实现的事实足以certificate我永远不会再使用全局variables。

另外,在不久的将来,当我们拥有数百个核心的时候,全球状态并不是真的会有太大的帮助。

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

为了加快优化函数调用,你可以做一些其他的事情,这些事情可能在全局variables黑客中performance得更好:

  • 将函数中局部variables的数量最小化为几个(显式)寄存器variables。
  • 最小化函数参数的数量,即通过使用结构指针而不是在相互调用的函数中使用相同的参数 – 星座。
  • 使function“裸”,这意味着它根本不使用堆栈。
  • 使用“适当的尾巴调用”(既不与java / -bytecode也不是java- / ecma-script)
  • 如果没有更好的办法,就像TABLES_NEXT_TO_CODE一样,把你自己的全局variables定位到函数代码旁边。 在函数式语言中,这是一个使用函数指针作为数据指针的后端优化; 但只要不用function语言编程,只需要将这些variables定位在函数所使用的那些之外即可。 然后再一次,你只希望这个从你的函数中删除堆栈处理。 如果你的编译器生成处理堆栈的汇编代码,那么这样做没有意义,你可以使用指针。

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

我可以给你这些标签用于谷歌search: – 适当的尾调用(它主要与函数式语言的命令后端相关) – TABLES_NEXT_TO_CODE(它主要与Haskell和LLVM相关)

但是当你经常使用全局variables的时候,你有'spagetti代码'。