13

私は自分が書くことができる最悪のコードをいじっていました(基本的に物事を壊そうとしました)、そして私はこのコードの断片に気づきました:

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;

実行速度を低下させるグローバル変数はどうなりますか?

4

5 に答える 5

10

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た。

私は巨大なファイルを手に入れましたが、いくつかのスマートな検索(私は日焼けを検索しました)で、私は欲しいものを見つけました:

関数から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

2番目の機能:

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が変更されていないことを確認できません。

constN()の宣言にaを追加するとconst int N=10000、ローカルバージョンよりもさらに高速になります。

    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は定数に置き換えられます。

于 2013-03-05T05:46:52.493 に答える
7

グローバルバージョンは、レジスターに入れるように最適化することはできません。

于 2013-03-05T05:15:26.977 に答える
7

@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;
}

結果は次のヒストグラムに表示されます(頻度/マイクロ秒):

両方の方法での比較出力時間のヒストグラム 赤いボックスは非グローバル変数ベースのforループ(N)であり、透明な緑はMがforループ(非グローバル)で終了しています。

externglobalvarialbeが少し遅いと疑う証拠があります。

答え を試す@rtpgの理由は非常に強力です。この意味で、グローバル変数は遅くなる可能性があります。

さまざまな最適化レベルでのgcc/g++のローカル変数とグローバル変数へのアクセス速度

この前提をテストするには、レジスタグローバル変数を使用してパフォーマンスをテストします。これはグローバル変数を持つ私のmain1.hでした

int N asm ("myN") = 10000;

新しい結果のヒストグラム:

レジスタグローバル変数を使用した結果

結論グローバル変数がレジスターにある場合、パフォーマンスが向上します。「グローバル」または「ローカル」変数の問題はありません。パフォーマンスは、変数へのアクセスによって異なります。

于 2013-03-05T06:35:11.837 に答える
5

tan上記のコードをコンパイルするとき、オプティマイザーは関数の内容を知らないと思います。

つまり、何をするのかtanは不明です。スタックに何かを詰め込み、あるアドレスにジャンプして、後でスタックをクリーンアップするだけです。

グローバル変数の場合、コンパイラは何をするかtanを知りませんN。ローカルの場合、合法的に取得できる「緩い」ポインタや参照はありませんNtanしたがって、コンパイラはどの値Nを取るかを知っています。

コンパイラーは、ループをフラット化できます-完全に(10000行の1つのフラットブロック)、部分的に(100の長さのループ、それぞれ100行)、またはまったくない(それぞれ1行の長さ10000ループ)、またはその間の任意の場所。

コンパイラーは、変数がローカルである場合、それらがどのように変化するか、または誰がそれらを読み取るかについてほとんど知識がないため、より多くのことを知っています。したがって、いくつかの仮定を行うことができます。

面白いことに、これは人間がグローバルについて推論するのが難しい理由でもあります。

于 2013-03-05T05:39:04.003 に答える
0

これが理由かもしれないと思います。グローバル変数はヒープメモリに格納されているため、コードは毎回ヒープメモリにアクセスする必要があります。上記の理由により、コードの実行が遅くなっている可能性があります。

于 2013-03-05T10:00:23.303 に答える