【问题标题】:Global variable reads inside tight loops in C全局变量在 C 中的紧密循环内读取
【发布时间】:2020-11-15 11:48:00
【问题描述】:

假设我在 C 中有一个紧密循环,在其中我使用全局变量的值来进行一些算术运算,例如

double c;
// ... initialize c somehow ...

double f(double*a, int n) {
  double sum = 0.0;
  int i;
  for (i = 0; i < n; i++) {
    sum += a[i]*c;
  }
  return sum;
}

使用c 全局变量。 c 在每次循环迭代中是否“从全局范围重新读取”​​?毕竟,它可能已经被其他一些执行其他功能的线程改变了,对吧?因此,通过在循环之前获取c 的本地(函数堆栈)副本并仅使用此副本,代码会更快吗?

double f(double*a, int n) {
  double sum = 0.0;
  int i;
  double c_cp = c;
  for (i = 0; i < n; i++) {
    sum += a[i]*c_cp;
  }
  return sum;
}

虽然我没有指定c 是如何初始化的,但我们假设它以某种方式完成,使得在编译时该值是未知的。此外,c 在整个运行时实际上是一个常量,即我作为程序员知道它的值不会改变。我可以让编译器了解这些信息吗,例如在全局范围内使用static double c?这会改变a[i]*ca[i]*c_cp 的问题吗?

我自己的研究

  • 阅读例如this 的“全局变量”部分,很明显,获取全局变量的本地副本是可行的方法。但是,他们想更新全局变量的值,而我只想读取它的值。
  • 使用godbolt 我没有注意到cc_cpdouble cstatic double c 的程序集有任何真正的区别。

【问题讨论】:

  • 您的第一个代码可以是optimized to read c only once
  • @MikeCAT 这与我的问题中的神螺栓链接有何不同(显然仍然让我感到困惑)?
  • c“可以”被其他线程更改,但不应该。根据 C 2018 5.1.2.4 中的规则,允许编译器将 c 视为没有被其他线程更改,因此程序员有责任确保不会发生这种情况。因此,在您显示的特定代码中,编译器可能会在每个函数调用中加载一次 c。但是,假设您的代码包含一个赋值,例如a[i] = …;。然后编译器无法知道a[i] 是否与c 相同(实际上,这只会被定义为i 等于0 或可能为-1),因此它必须防止这种情况发生。

标签: c performance stack global-variables global


【解决方案1】:

任何相当聪明的编译器都会优化您的代码,使其表现得像您的第二个代码 sn-p。使用 static 不会有太大变化,但如果您想确保每次迭代都读取,请使用 volatile

关于来自不同线程的更改的要点很好。就单线程执行而言,编译器将保持代码的完整性。这意味着它可以重新排序您的代码、跳过某些内容、添加一些内容——只要最终结果仍然相同。

对于多个线程,您的工作是确保事情仍然以特定的顺序发生,而不仅仅是最终结果是正确的。确保是内存屏障的方法。这是一个有趣的话题,但最好避免阅读,除非您是专家。

【讨论】:

  • 啊,我明白了。所以volatile 与我想要的完全相反。我想要的确实是默认行为。此外,线程“问题”不是编译器要处理的工作,因此不会导致它错过优化。我想我明白了! :-)
【解决方案2】:

一旦将所有内容转换为机器代码,您将不会有任何区别。如果 c 是全局的,对 c 的任何访问都将引用 c 的地址,或者很可能在紧密循环中 c 将保存在寄存器中,或者在最坏的情况下保存在 L1 缓存中。

在 Linux 机器上,您可以轻松生成程序集并检查生成的代码。

您还可以运行基准测试。

【讨论】:

  • 组件,请参阅问题中的godbolt链接。你能解释一下c如何保存在寄存器中,当其他线程可能改变值时,如问题中所述?
  • 正如@TomoCeferin 所述,使用 volatile 将确保使用 c 的最新值(假设多线程)。也就是说,我只是将 c 作为参数传递。它更干净,最后与使用堆栈上的 c_cp = c 没有太大区别。
  • 一般我会同意。在我的实际代码中,我有数百个全局变量,我真的不想传递(甚至收集到struct),因为它会导致体贴的混乱。无论如何,这不是问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-14
  • 2021-04-24
  • 2011-04-24
  • 2016-03-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多