【问题标题】:Global Variables performance effect (c, c++)全局变量性能效果 (c, c++)
【发布时间】:2011-03-06 13:35:03
【问题描述】:

我目前正在开发一种非常快速的算法,其中一部分是非常快速的扫描仪和统计功能。 在这个任务中,我追求任何性能优势。 因此,我也对保持代码“多线程”友好感兴趣。

现在的问题: 我注意到将一些非常频繁访问的变量和数组放入“全局”或“静态本地”(同样如此),会有可衡量的性能优势(在 +10% 的范围内)。 我试图了解原因并找到解决方案,因为我宁愿避免使用这些类型的分配。 请注意,我不认为差异来自“分配”,因为在堆栈上分配一些变量和小数组几乎是瞬时的。我相信区别来自“访问”和“修改”数据。

在此搜索中,我从 stackoverflow 中找到了这篇旧帖子: C++ performance of global variables

但我对那里的答案感到非常失望。很少有解释,主要是在咆哮“你不应该那样做”(嘿,这不是问题!)和非常粗略的陈述,比如“它不会影响性能”,这显然是不正确的,因为我正在用精确的方法测量它基准工具。

如上所述,我正在寻找一个解释,如果存在的话,这个问题的解决方案。到目前为止,我感觉计算本地(动态)变量的内存地址比全局(或本地静态)的成本要高一些。也许类似于 ADD 操作差异。但这无助于寻找解决方案...

【问题讨论】:

  • 您是否将每次重新计算该值所需的时间与从全局变量中检索该值所需的时间进行比较?是的,这总是会更快。但是没有理由说全局变量会比局部变量快。我不确定这里的问题是什么。
  • 我正在比较收集统计数据所需的时间,使用相同的代码,但一个将结果存储/更新到本地结构中,另一个使用全局(或本地静态)结构。 “本地静态”获胜,放下手,其他一切都一样。
  • 在没有看到实际代码的情况下很难说出任何事情。我们甚至不知道您要替换什么机制。
  • 不确定“提供源代码”是否有用,但是,如果一个例子有帮助,让我们说这个:“ int stats[256]; while (p
  • @Cyan:您是否对您给出的特定示例进行了基准测试?它是否显示了 10% 的性能差异?如果不是,那么它不是一个有用的例子。反汇编真实代码,查看访问变量的部分,看看它是否可以合理地占运行时的 10%。如果不是,那么一定有一些微妙的连锁效应,这可能是您的真实代码所特有的。如果您无法生成显示问题的示例,那么除非原因非常明显,否则您将很难分析其性能,否则任何人都将很难。它似乎不在这里:-)

标签: c performance static benchmarking global


【解决方案1】:

这实际上取决于您的编译器、平台和其他细节。但是,我可以描述一种全局变量更快的场景。

在许多情况下,全局变量处于固定偏移量。这允许生成的指令直接使用该地址。 (类似于MOV AX,[MyVar]。)

但是,如果您有一个与当前堆栈指针或类或数组的成员相关的变量,则需要进行一些数学运算来获取数组的地址并确定实际变量的地址。

显然,如果您需要在全局变量上放置某种互斥锁以使其保持线程安全,那么您几乎肯定会失去任何性能提升。

【讨论】:

  • 我完全同意你的说法:互斥锁只会减慢一切。我什至尝试分配几个表并在启动该功能时选择一个“空闲表”;它可以工作,但与局部变量具有相同的性能损失;所以这是无用的复杂性
  • “需要一些数学运算” - 通常不同之处在于,对于全局或局部静态,地址可能是一个常量修正。对于自动变量,地址将是应用于当前堆栈指针的常量偏移量。根据 CPU 提供的寻址模式,这可能会或可能不会真正影响性能。
  • @Steve:是的,这就是为什么我说它取决于编译器和平台。请注意,我在考虑非静态局部变量。我会假设大多数静态局部变量的存储方式类似于全局变量的存储方式。
  • @Jonathan:对不起,我不是想反对,只是扩大。此外,如果优化器注意到它正在执行 sp+187 很多,并且这可能会花费时间,那么它可以将 sp+187 的值缓存在寄存器中,或者如果操作系统将 sp 移动到例程的一部分中不介意。这些是提问者在断定您的示例适用于这种情况之前需要检查的内容。
  • @Steve:是的,对于这种级别的优化,编译器生成的汇编语言的转储应该是有序的。
【解决方案2】:

如果它们是 POD 类型,则创建局部变量实际上是免费的。您可能因堆栈变量过多或其他类似的基于对齐的原因而溢出缓存行,这些原因非常特定于您的代码段。我通常发现非局部变量会显着降低性能。

【讨论】:

  • 您的陈述在大多数“正常”情况下都是正确的,但在这里我正在对差异进行基准测试以支持非局部变量。所以这不是“如果它发生”的问题,因为它确实发生了。问题是“为什么”。
  • @Cyan:我相信我可能已经提出了一个答案,比如缓存行溢出。请务必阅读完整答案。
  • @Jens Gustedt:普通旧数据,例如整数等
  • 别觉得被冒犯了!是的,我已经阅读了您的答案,并且我已经同意分配没有区别这一事实。现在你对缓存行的评论对我来说不是很清楚。堆栈中创建的数据量 (~3K) 远低于 L1 缓存大小 (~32K),因此它应该完全适合 L1 缓存。我不知道如何确保它是否与缓存行对齐(即 64 的地址倍数),也不知道它是否会产生任何影响。事实上,我已经对此进行了一些测试(通过使其中一张表更大或更小),但似乎没有帮助。
  • 变量很重,请不要这样开玩笑
【解决方案3】:

在速度方面很难超越静态分配,虽然 10% 的差异很小,但可能是由于地址计算。

但是,如果您正在寻找速度, 您在评论 while(p<end)stats[*p++]++; 中的示例显然是展开的候选者,例如:

static int stats[M];
static int index_array[N];
int *p = index_array, *pend = p+N;
// ... initialize the arrays ...
while (p < pend-8){
  stats[p[0]]++;
  stats[p[1]]++;
  stats[p[2]]++;
  stats[p[3]]++;
  stats[p[4]]++;
  stats[p[5]]++;
  stats[p[6]]++;
  stats[p[7]]++;
  p += 8;
}
while(p<pend) stats[*p++]++;

不要指望编译器为你做这件事。它可能会也可能无法弄清楚。

想到其他可能的优化,但它们取决于您实际尝试做的事情。

【讨论】:

  • 谢谢迈克。好建议。事实上,我已经在展开了。这就是为什么我说这只是一个“示例”,因为完整的代码有点复杂,对这个讨论没有任何用处。
  • @Cyan:我想到的问题是——为什么是间接数组?设置多久更改一次?这可能是预编译的候选者吗?类似的东西。
  • @Mike:有趣的点。你叫什么“间接数组”?预编译是一个很好的优化路线。这就是为什么我喜欢关于模板的吸引的想法,这是一种预编译参数之一的方法(只要它具有有限数量的值)。
  • @Cyan:在这种情况下,您通过首先索引另一个数组index_array 来索引stats,然后您正在循环遍历它。这意味着您只想增加stats 的子集(因为显然顺序无关紧要)。如果设置index_array 仅在低频下完成,您也可以通过简单地打印一个函数来增加stats 的所需成员,即时编译和链接一个dll,动态加载它,然后执行您的操作递增非常快。这就是你真正想要做什么的意思?
  • 好的; index_array 特定于您的示例,但不反映我正在处理的代码。在我的例子中,我们从一个缓冲区开始,它被定义为一个 char* 和一个长度。然后我们遍历缓冲区,计算其中的 char 值。因此,值介于 0 到 255 之间。
【解决方案4】:

如果你有类似的东西

int stats[256]; while (p<end) stats[*p++]++;

static int stats[256]; while (p<end) stats[*p++]++;

您并没有真正比较同一事物,因为首先您没有对数组进行初始化。明确写第二行相当于

static int stats[256] = { 0 }; while (p<end) stats[*p++]++;

为了公平起见,您应该先阅读

 int stats[256] = { 0 }; while (p<end) stats[*p++]++;

如果他的变量处于已知状态,你的编译器可能会推导出更多的东西。

那么,static 案例可能具有运行时优势,因为初始化是在编译时(或程序启动)完成的。

要测试这是否弥补了您的差异,您应该多次使用静态声明和循环运行相同的函数,看看如果您的调用次数增加,差异是否会消失。

但正如其他人已经说过的,最好检查编译器生成的汇编器,看看生成的代码有什么有效差异。

【讨论】:

  • OK,两个版本都在进入函数时初始化。我没有在示例中转录这部分,但它确实以一个简单的开头:“ for (=0; i
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-03
  • 1970-01-01
  • 2015-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多