Pertanyaan Variabel global memperlambat kode


Saya main-main dengan kode terburuk yang bisa saya tulis, (pada dasarnya mencoba memecahkan banyak hal) dan saya perhatikan bahwa kode ini:

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

di mana N adalah variabel global berjalan secara signifikan lebih lambat maka:

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

Apa yang terjadi dengan variabel global yang membuatnya berjalan lebih lambat?


10
2018-03-05 05:09


asal


Jawaban:


tl; dr: Versi lokal menyimpan N dalam daftar, versi global tidak. Deklarasikan konstanta dengan const dan itu akan lebih cepat tidak peduli bagaimana Anda mendeklarasikannya.


Berikut ini contoh kode yang saya gunakan:

#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();
}

(bernama test.cpp).

Untuk melihat kode assembler yang dihasilkan saya berlari g++ -S test.cpp.

Saya mendapat file besar tetapi dengan beberapa pencarian pintar (saya mencari cokelat), saya menemukan apa yang saya inginkan:

dari first fungsi:

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

fungsi kedua:

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

Jadi dari kode assembler Anda dapat melihat (atau tidak) bahwa, dalam versi lokal, N disimpan dalam daftar selama seluruh perhitungan, sedangkan dalam versi global, N dibaca kembali dari global pada setiap iterasi.

Saya membayangkan alasan utama mengapa ini terjadi adalah untuk hal-hal seperti threading, compiler tidak dapat memastikan bahwa N tidak dimodifikasi.

jika Anda menambahkan const untuk deklarasi N (const int N=10000), itu akan lebih cepat daripada versi lokal sekalipun:

    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 diganti dengan konstanta.


8
2018-03-05 05:46



Versi global tidak dapat dioptimalkan untuk memasukkannya ke dalam daftar.


7
2018-03-05 05:15



Saya melakukan sedikit percobaan dengan pertanyaan dan jawaban dari @ rpg,

bereksperimen dengan pertanyaan itu

Di file main1.h variabel N global

int N = 10000;

Kemudian di file main1.c, 1000 perhitungan situasi:

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

Hasilnya ditunjukkan dalam histogram (frekuensi / mikrodetik) berikut:

the histogram for the comparison output time in both methods Kotak merah adalah variabel non global berbasis berakhir untuk loop (N), dan hijau transparan M berakhir berdasarkan loop (non global).

Ada bukti yang mencurigai bahwa varialbe global ekstern sedikit lambat.

bereksperimen dengan jawabannya Alasan dari @ rpg sangat kuat. Dalam pengertian ini, variabel global bisa lebih lambat.

Kecepatan mengakses variabel lokal vs. global di gcc / g ++ pada tingkat pengoptimalan yang berbeda

Untuk menguji premis ini saya menggunakan variabel global register untuk menguji kinerja. Ini adalah main1.h saya dengan variabel global

int N asm ("myN") = 10000;

Histogram hasil baru:

Results with register global variable

kesimpulan ada peningkatan kinerja ketika variabel global dalam daftar. Tidak ada masalah variabel "global" atau "lokal". Kinerja tergantung pada akses ke variabel.


7
2018-03-05 06:35



Saya mengasumsikan optimizer tidak tahu isi tan berfungsi saat menyusun kode di atas.

Ie, apa tan tidak diketahui - semua yang diketahui adalah untuk memasukkan barang ke dalam stack, melompat ke beberapa alamat, lalu bersihkan tumpukan sesudahnya.

Dalam kasus variabel global, compiler tidak tahu apa tan tidak untuk N. Dalam kasus lokal, tidak ada petunjuk atau referensi yang "longgar" N bahwa tan sah bisa mendapatkan: jadi compiler tahu apa nilai-nilai Nakan mengambil.

Compiler dapat meratakan loop - di mana saja dari sepenuhnya (satu blok datar 10.000 baris), sebagian (100 loop panjang, masing-masing dengan 100 baris), atau tidak sama sekali (panjang 10.000 loop dari 1 baris masing-masing), atau apa pun di antara .

Compiler tahu jauh lebih banyak ketika variabel Anda bersifat lokal, karena ketika mereka global itu memiliki sangat sedikit pengetahuan tentang bagaimana mereka berubah, atau yang membacanya. Jadi sedikit asumsi yang bisa dibuat.

Amusingly, ini juga mengapa sulit bagi manusia untuk berpikir tentang global.


5
2018-03-05 05:39



Saya pikir ini mungkin alasannya: Karena variabel Global disimpan dalam memori heap, kode Anda harus mengakses memori heap setiap kali. Mungkin karena kode alasan di atas berjalan lambat.


0
2018-03-05 10:00