C语言中全局变量的性能影响

5
我有五个函数,平均被调用10000次以上。所有函数都会修改/使用某些变量。
我知道使用全局变量是不好的做法。但是为了性能考虑,保持它们为全局变量而不传递它们是否有意义 - 特别是当我这么多次调用函数时?
或者说,从性能上来看,我不会获得太多好处吗?

不,我还没有。我仍然处于设计阶段,所以想从一开始就把事情做好。 - hari
7
引入全局变量是从一开始就让事情出现问题的一种确定的方式。 - R.. GitHub STOP HELPING ICE
2
顺便说一下,我认为以正确的方式(无全局变量)完成它的最大“成本”不在于性能,而在于打字。全局变量是为了解决程序员的懒惰问题而设计的,而不是性能。 - R.. GitHub STOP HELPING ICE
1
我只是想确认一下,您实际上并不知道这是否是一个问题,而您正在基于此做出设计决策。是这样吗?作为一个长期从事性能调优的人,当我让程序运行后,第一件事就是获取一些随机堆栈样本。这些样本告诉我应该在哪里进行优化。猜测并不真正起作用。 - Mike Dunlavey
@Mike:我完全同意你的观点。但这听起来像是一个非常基础的问题,我想要一些专家建议所以才问的。谢谢。 - hari
7个回答

8
不要为了性能而引入全局变量/全局状态。这是错误的,与所有良好的编码实践相悖,并且通常不会提高性能(甚至可能会降低性能)。
如果您发现传递大量变量太昂贵,可以将它们全部放在上下文结构体中,并传递一个指向该结构体的单个指针。这样,您就避免了创建全局状态(即使是静态存储期变量也是全局状态),从而防止您的代码在多个实例中被使用。成本几乎为零,并且实际上比位置无关代码(共享库或位置无关可执行文件)中的全局变量更少成本。

哎呀,我正要完成我的回答,建议传递一个指向结构体的指针 :( - Vinicius Kamakura
@R..:讲解得非常好 :) 非常感谢。当您提到结构体时,这里的“上下文”是什么意思? - hari
“Context”只是一个通用词,用于描述属于特定实例的状态变量集合。 - R.. GitHub STOP HELPING ICE
尽管有这个答案,使用全局变量确实是一种合法的优化技术 - 但是,它应该作为“最后的手段”使用,只有在整个应用程序已经编写和分析并且您仍然试图从瓶颈例程中削减那些关键的几个纳秒。在程序的设计点上,你甚至不应该考虑任何这些,@hari。 - BlueRaja - Danny Pflughoeft
1
我仍然会说这取决于你写的是什么,如果你因为“优化”而犯下如此可怕的错误,应该可以通过简单的条件编译/预处理来禁用它。您获得的不到1%的性能在编写代码后的几个月内并不重要,但劣势将在几个月甚至数年之后回来困扰您,当您意识到需要处理多个实例并且必须修复您甚至无法记起写过的代码时... - R.. GitHub STOP HELPING ICE

5
您可以通过减少传递给函数的参数数量,通过预分配变量(例如全局或静态变量),来看到一些小的性能提升。
性能变化绝对取决于许多因素,其中最重要的是您正在开发的平台。如果您正在为微型微处理器开发,则从调用函数复制参数到堆栈上所需的时间以及访问堆栈所需的时间可能会占总执行时间的足够大的比例,以证明这种优化是有必要的。
请注意,在传递参数所需的时间显着的情况下,您可能会发现其他一些建议(例如传递指向结构体的指针,传递指向静态变量的指针)也不会带来任何好处。使用全局变量确实使编译器/链接器有机会硬编码访问这些变量,而不必间接地从堆栈上的指针访问它们。这对于没有缓存的处理器尤其相关。
当然,这完全取决于目标,高度依赖于您正在使用的处理器的指令集。在任何具有合理指令集的平台上,您都应该看到一些改进。

然而,在采取这样的措施之前,应该对此代码进行分析。在大多数平台上,对于任何非微不足道的函数,传递参数和访问它们所需的时间都是微不足道的。任何潜在的性能提升都将以更难维护的代价为代价。

通过使用其他优化技术,很可能会实现更大的性能提升。查看 this 问题以了解一些要尝试的方法。


编辑:我从您的评论中看到,您仍处于该项目的设计阶段。
现在进行这样的优化还为时过早。在这个阶段,通过优化使用的算法,比像这样在指令级别上最小化对性能的影响要更有影响力。

感謝 Andrew 在回覆結尾提供的連結,非常感激。 - hari

3
在C语言中,静态变量具有文件作用域,如果您可以将函数分组到一个文件中,则它们可能足够使用。对于我来说,静态变量比全局变量少了几个数量级的问题。
一个经常被忽视的问题是,在函数体内声明的变量将分配在堆栈上,而静态变量通常从一个名为bss的不受限制的内存池中分配。因此,将所有变量整齐地定义在函数内部可能会导致堆栈耗尽问题,可以通过使用静态变量以一种相当清晰的方式避免这种情况。

可以使用静态局部变量作为非常好的解决方案。+1 - Vinicius Kamakura
@fvu:谢谢,你能否提供一个指针/参考,解释为什么静态变量比全局变量更好? - hari
@fvu:谢谢,但你在第二段解释的情况只适用于递归函数调用,是吗? - hari
@hari 可见性。全局范围的变量对构成可执行程序的所有文件都是“可见”的。静态变量仅在其定义的源文件中的函数内部可见,其他地方不可见。 - fvu
@fvu:好的,谢谢您的解释。我更想知道在没有递归调用的情况下,如何出现“堆栈耗尽问题”。 - hari
@hari no - 如果函数1需要1MB的本地变量并调用f2,那么在执行f2期间栈上已经需要2MB的空间。我认为Linux中的标准栈大小为8MB,这并不是非常巨大的。当然,计算机系统越小,你就会更快地遇到这个问题。 - fvu

2
我认为全局变量可能比传递参数慢。参数存在于频繁使用的堆栈中,因此很可能在缓存中。全局变量存在于不太常用的静态空间中,因此不太可能在缓存中,使内存查找变得更慢。由于缓存考虑,您的跳转(到新函数)可能是整个函数调用操作中最慢的部分。
如果您的函数很小,请考虑将它们内联。如果它们很大,推送一两个字到堆栈上将会产生最小的差异。
还要注意,使用堆栈进行参数传递非常依赖x86。ARM和其他具有大量寄存器的体系结构通常会使用一些寄存器进行参数传递,这是非常快速的。

2
在一些嵌入式微控制器上,使用全局变量而不是参数可能会有性能优势,但这高度依赖于机器。
例如,在使用典型的8位微控制器编译器(HT-PICC18)时,设置全局变量的成本为每字节两个指令/两个周期。传递一个单字节参数的成本为一个指令/一个周期。传递两个或更多字节的参数的成本为每字节两个周期。因此,传递两个单字节参数的最有效方法是将一个作为参数传递,另一个作为全局变量传递。
在68HC11的Introl编译器上,具有任何自动变量或参数的例程需要省略所有变量都是静态的并且参数作为全局变量传递的多指令序言和尾声。然而,如果一个例程将使用任何局部变量或参数,则使用局部变量和参数来处理其他所有内容的边际成本是可以忽略不计的。
在ARM上,即使没有缓存,相反的情况通常适用:访问自动变量或参数通常比访问全局变量更快。自动变量和参数将在寄存器中,或者将在从存储在寄存器中的地址的已知偏移处直接访问。相比之下,访问全局变量通常是一个两步过程:首先加载全局变量的地址,然后访问变量本身。

1

一般人们会尽量避免使用全局参数,除非确实需要它们(即除非某些东西真正具有全局状态)。特别是对于多线程应用程序,使用全局参数可能会使事情比必要的更加困难。

就性能而言,我听说有些人暗示访问全局变量在某些情况下可能会更快,尽管我觉得很难相信,但唯一确定的方法是为您的特定情况实际进行一些基准测试。

就个人而言,我永远不会这样做。我会考虑将参数传递给函数的方式(例如,在处理大型数据类型的情况下,请确保通过指针而不是通过复制来传递),并确保向编译器传递正确(最佳)的优化设置。

希望这可以帮助到您。


0

我认为通过用全局变量替换参数来提高性能是不可行的。

无论如何,如果你想这么做,你也可以考虑在同一文件中声明所有函数和变量,并将变量声明为静态。这样只有在同一文件中声明的函数才能访问它们。

请看这里


@R.. 请解释一下您的评论。 - Fabio
4
全局符号名称最多只有全局变量的坏处的1%。毕竟,你可以选择一种特殊的命名方案来消除这个不良方面。全局变量99%的坏处是它们引入了全局状态,对于外部和内部(静态)链接变量同样适用。全局状态意味着处理该状态的程序部分只有一个“实例”,并且它不能安全地用作库代码。在大约四到五年后,通常会发现这个想法好像很好... - R.. GitHub STOP HELPING ICE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接