Глобальные переменные замедляют код
Я возился с худшим кодом, который мог написать, (в основном пытался сломать вещи), и я заметил, что этот фрагмент кода:
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;
Что происходит с глобальной переменной, которая заставляет ее работать медленнее?
Ответы
Ответ 1
tl; dr: локальная версия хранит N в регистре, а глобальная версия - нет. Объявляйте константы с константой, и она будет быстрее, независимо от того, как вы ее объявите.
Вот пример кода, который я использовал:
#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
вторая функция:
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 не изменен.
если вы добавите const
в объявление N (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 заменяется константой.
Ответ 2
Глобальная версия не может быть оптимизирована для размещения ее в регистре.
Ответ 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 заканчивается на основе цикла (не глобальная).
Есть доказательства того, что глобальный varialbe extern немного медленный.
экспериментируя с ответом
Причина @rtpg очень сильна. В этом смысле глобальная переменная может быть медленнее.
Скорость доступа к локальным и глобальным переменным в gcc/g++ на разных уровнях оптимизации
Чтобы проверить эту предпосылку, я использую глобальную переменную регистра для проверки производительности.
Это был мой main1.h с глобальной переменной
int N asm ("myN") = 10000;
Новая гистограмма результатов:
![Results with register global variable]()
заключение показатели производительности улучшаются, когда глобальная переменная находится в регистре. Нет никакой "глобальной" или "локальной" переменной проблемы. Производительность зависит от доступа к переменной.
Ответ 4
Я предполагаю, что оптимизатор не знает содержимое функции tan
при компиляции вышеуказанного кода.
То, что tan
делает, неизвестно - все, что он знает, это набивать вещи в стек, переходить на какой-то адрес, а затем очищать стек потом.
В случае глобальной переменной компилятор не знает, что tan
делает для N
. В локальном случае нет "свободных" указателей или ссылок на N
, на которые tan
мог бы законно получить: так что компилятор знает, какие значения N
будут приняты.
Компилятор может сгладить петлю - от полностью (один плоский блок из 10000 строк), частично (100 циклов длины, каждый со 100 линиями) или совсем нет (длина 10000 циклов по 1 строке каждая) или ничего между ними.
Компилятор знает больше, когда ваши переменные являются локальными, потому что, когда они являются глобальными, у него очень мало знаний о том, как они меняются, или кто их читает. Таким образом, можно сделать несколько предположений.
Как забавно, это также объясняет, почему людям трудно рассуждать о глобальных признаках.
Ответ 5
Я думаю, что это может быть причиной:
Поскольку глобальные переменные хранятся в памяти кучи, ваш код должен каждый раз получать доступ к памяти кучи.
Возможно, из-за выше кода причины работает медленнее.