【问题标题】:Global variables slow down code全局变量减慢代码
【发布时间】:2018-01-08 17:38:14
【问题描述】:

我正在乱写我能写的最糟糕的代码,(基本上是试图破坏东西),我注意到这段代码:

for(int i = 0; i < N; ++i)
    tan(tan(tan(tan(tan(tan(tan(tan(x++))))))));
end
std::cout << x;

其中 N 是一个全局变量,然后运行速度明显变慢:

int N = 10000;
for(int i = 0; i < N; ++i)
    tan(tan(tan(tan(tan(tan(tan(tan(x++))))))));
end
std::cout << x;

全局变量会怎样导致运行速度变慢?

【问题讨论】:

  • 我想编译器可能会怀疑xtan 函数内被修改,这会阻止大量优化。不过,我不确定这是否会发生在这里。
  • @Pubby 唯一的区别是N减速吗?
  • 好吧,如果x 是本地的,那么显然它不能在tan 内部修改。所以声明很重要。

标签: c++ performance global-variables


【解决方案1】:

tl;dr:本地版本将 N 保存在寄存器中,全局版本不保存。用 const 声明常量,不管怎么声明都会更快。


这是我使用的示例代码:

#include <iostream>
#include <math.h>
void first(){
  int x=1;
  int N = 10000;
  for(int i = 0; i < N; ++i)
    tan(tan(tan(tan(tan(tan(tan(tan(x++))))))));
  std::cout << x;
}
int N=10000;
void second(){
  int x=1;
  for(int i = 0; i < N; ++i)
    tan(tan(tan(tan(tan(tan(tan(tan(x++))))))));
  std::cout << x;
}
int main(){
  first();
  second();
}

(命名为test.cpp)。

为了查看生成的汇编代码,我运行了g++ -S test.cpp

我得到了一个巨大的文件,但通过一些智能搜索(我搜索了 tan),我找到了我想要的:

来自first 函数:

Ltmp2:
    movl    $1, -4(%rbp)
    movl    $10000, -8(%rbp) ; N is here !!!
    movl    $0, -12(%rbp)    ;initial value of i is here
    jmp LBB1_2       ;goto the 'for' code logic
LBB1_1:             ;the loop is this segment
    movl    -4(%rbp), %eax
    cvtsi2sd    %eax, %xmm0
    movl    -4(%rbp), %eax
    addl    $1, %eax
    movl    %eax, -4(%rbp)
    callq   _tan
    callq   _tan
    callq   _tan
    callq   _tan
    callq   _tan        
    callq   _tan
    callq   _tan
    movl    -12(%rbp), %eax
    addl    $1, %eax
    movl    %eax, -12(%rbp) 
LBB1_2:
    movl    -12(%rbp), %eax ;value of n kept in register 
    movl    -8(%rbp), %ecx  
    cmpl    %ecx, %eax  ;comparing N and i here
    jl  LBB1_1      ;if less, then go into loop code
    movl    -4(%rbp), %eax

第二个功能:

Ltmp13:
    movl    $1, -4(%rbp)    ;i
    movl    $0, -8(%rbp) 
    jmp LBB5_2
LBB5_1:             ;loop is here
    movl    -4(%rbp), %eax
    cvtsi2sd    %eax, %xmm0
    movl    -4(%rbp), %eax
    addl    $1, %eax
    movl    %eax, -4(%rbp)
    callq   _tan
    callq   _tan
    callq   _tan
    callq   _tan
    callq   _tan
    callq   _tan
    callq   _tan
    movl    -8(%rbp), %eax
    addl    $1, %eax
    movl    %eax, -8(%rbp)
LBB5_2:
    movl    _N(%rip), %eax  ;loading N from globals at every iteration, instead of keeping it in a register
    movl    -8(%rbp), %ecx

所以从汇编代码中你可以看到(或看不到),在本地版本中,N 在整个计算过程中保存在寄存器中,而在全局版本中,N 在每次迭代时从全局中重新读取。

我想发生这种情况的主要原因是线程之类的事情,编译器无法确定 N 没有被修改。

如果在 N (const int N=10000) 的声明中添加 const,它会比本地版本更快:

    movl    -8(%rbp), %eax
    addl    $1, %eax
    movl    %eax, -8(%rbp)
LBB5_2:
    movl    -8(%rbp), %eax
    cmpl    $9999, %eax ;9999 used instead of 10000 for some reason I do not know
    jle LBB5_1

N 被一个常数代替。

【讨论】:

  • 如果在编译时启用优化会得到什么?
  • 线程不会出现在编译器的决策过程中,因为竞争条件是未定义的行为。每次迭代都会加载N,因为编译器无法确定tan 不会修改N
  • 这看起来像是一个严重的优化失败:像N 这样的未修改且“未公开”的局部变量可以被视为在初始化后从未修改过,因此即使没有const,也可以替换为常量。
【解决方案2】:

无法优化全局版本以将其放入寄存器中。

【讨论】:

  • @Krishnabhadra:在这种情况下不是。问题是编译器不知道没有其他人会使用它。
【解决方案3】:

我对@rtpg的问题和答案做了一个小实验,

用问题做实验

在文件 main1.h 中的全局 N 变量

int N = 10000;

然后在main1.c文件中,1000次计算的情况:

#include <stdio.h>
#include "sys/time.h"
#include "math.h"
#include "main1.h"



extern int N;

int main(){

        int k = 0;
        timeval static_start, static_stop;
        int x = 0;

        int y = 0;
        timeval start, stop;
        int M = 10000;

        while(k <= 1000){

                gettimeofday(&static_start, NULL);
                for (int i=0; i<N; ++i){
                        tan(tan(tan(tan(tan(tan(tan(tan(x++))))))));
                }
                gettimeofday(&static_stop, NULL);

                gettimeofday(&start, NULL);
                for (int j=0; j<M; ++j){
                        tan(tan(tan(tan(tan(tan(tan(tan(y++))))))));
                }
                gettimeofday(&stop, NULL);

                int first_interval = static_stop.tv_usec - static_start.tv_usec;
                int last_interval = stop.tv_usec - start.tv_usec;

                if(first_interval >=0 && last_interval >= 0){
                        printf("%d, %d\n", first_interval, last_interval);
                }

                k++;
        }

        return 0;
}

结果显示在以下直方图中(频率/微秒):

红色框是基于非全局变量的循环结束(N),透明的绿色是基于 M 结束的 for 循环(非全局)。

有证据怀疑外部全局变量有点慢。

试验答案 @rtpg 的原因非常强大。从这个意义上说,全局变量可能会更慢。

Speed of accessing local vs. global variables in gcc/g++ at different optimization levels

为了测试这个前提,我使用一个寄存器全局变量来测试性能。 这是我的带有全局变量的 main1.h

int N asm ("myN") = 10000;

新的结果直方图:

结论当全局变量在寄存器中时,性能会有所提升。不存在“全局”或“局部”变量问题。性能取决于对变量的访问。

【讨论】:

    【解决方案4】:

    我假设优化器在编译上述代码时不知道tan 函数的内容。

    也就是说,tan 所做的事情是未知的——它所知道的只是将东西塞入堆栈,跳转到某个地址,然后清理堆栈。

    在全局变量的情况下,编译器不知道tanN 做了什么。在本地情况下,没有tan 可以合法获取的“松散”指针或对N 的引用:因此编译器知道N 将采用什么值。

    编译器可以使循环变平——从完全(一个 10000 行的扁平块)、部分(100 个长度的循环,每个循环有 100 行)或根本不(长度为 10000 个循环,每个循环 1 行),或介于两者之间。

    当你的变量是局部变量时,编译器知道得更多,因为当它们是全局变量时,它对它们如何变化或谁读取它们知之甚少。可以做的假设很少。

    有趣的是,这也是人类难以推理全局变量的原因。

    【讨论】:

      【解决方案5】:

      我认为这可能是一个原因: 由于全局变量存储在堆内存中,因此您的代码每次都需要访问堆内存。 可能是因为上述原因代码运行缓慢。

      【讨论】:

      • 全局变量存储在STACK的DATA段中,而不是堆中。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-12-23
      • 2017-10-06
      • 1970-01-01
      • 2013-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多