Эффект производительности глобальных переменных (c, С++)

В настоящее время я разрабатываю очень быстрый алгоритм, причем одна его часть представляет собой чрезвычайно быстрый сканер и функцию статистики. В этом квесте я получаю любое преимущество в производительности. Поэтому я также заинтересован в поддержании дружественного кода "multi-thread".

Теперь вопрос: я заметил, что включение очень часто используемых переменных и массивов в "Глобальный" или "статический локальный" (что делает то же самое), существует измеримое преимущество в производительности (в диапазоне + 10%). Я пытаюсь понять, почему, и найти решение об этом, так как я предпочел бы избежать использования этих типов распределения. Обратите внимание, что я не думаю, что разница происходит от "распределения", так как выделение нескольких переменных и небольшого массива в стеке почти мгновенно. Я считаю, что разница связана с "доступом" и "изменением" данных.

В этом поиске я нашел это старое сообщение из stackoverflow: С++ производительность глобальных переменных

Но я очень разочарован ответами там. Очень мало объяснений, в основном разглагольствования о том, "вы не должны этого делать" (эй, это не вопрос!) И очень грубые высказывания, такие как "это не влияет на производительность", что явно неверно, поскольку я измеряю его с точными контрольные инструменты.

Как было сказано выше, я ищу объяснение и, если оно существует, решение этой проблемы. До сих пор у меня было ощущение, что вычисление адреса памяти локальной (динамической) переменной стоит немного больше, чем глобальный (или локальный статический). Возможно, что-то вроде разницы в работе ADD. Но это не помогает найти решение...

Ответ 1

Это действительно зависит от вашего компилятора, платформы и других деталей. Однако я могу описать один сценарий, где глобальные переменные быстрее.

Во многих случаях глобальная переменная имеет фиксированное смещение. Это позволяет сгенерированным инструкциям просто использовать этот адрес напрямую. (Что-то вдоль линий MOV AX,[MyVar].)

Однако, если у вас есть переменная, относящаяся к текущему указателю стека или члену класса или массива, для вычисления адреса массива требуется определить некоторую математику и определить адрес фактической переменной.

Очевидно, что если вам нужно поместить какую-то мьютекс в вашу глобальную переменную, чтобы поддерживать ее в потоковом режиме, то вы почти наверняка больше потеряете прирост производительности.

Ответ 2

Создание локальных переменных может быть буквально бесплатным, если они являются типами POD. Вероятно, вы переполняете строку кэша слишком большим количеством переменных стека или другими аналогичными причинами, основанными на выравнивании, которые очень специфичны для вашего фрагмента кода. Обычно я обнаруживаю, что нелокальные переменные значительно снижают производительность.

Ответ 3

Тяжело бить статическое распределение для скорости, а 10% - довольно небольшая разница, это может быть связано с вычислением адреса.

Но если вы ищете скорость, ваш пример в комментарии while(p<end)stats[*p++]++; является очевидным кандидатом для разворачивания, например:

static int stats[M];
static int index_array[N];
int *p = index_array, *pend = p+N;
// ... initialize the arrays ...
while (p < pend-8){
  stats[p[0]]++;
  stats[p[1]]++;
  stats[p[2]]++;
  stats[p[3]]++;
  stats[p[4]]++;
  stats[p[5]]++;
  stats[p[6]]++;
  stats[p[7]]++;
  p += 8;
}
while(p<pend) stats[*p++]++;

Не рассчитывайте на компилятор, чтобы сделать это за вас. Он может или не сможет понять это.

Другие возможные оптимизации приходят на ум, но они зависят от того, что вы на самом деле пытаетесь сделать.

Ответ 4

Если у вас есть что-то вроде

int stats[256]; while (p<end) stats[*p++]++;

static int stats[256]; while (p<end) stats[*p++]++;

вы на самом деле не сравниваете одно и то же, потому что для первого экземпляра вы не выполняете инициализацию своего массива. Написанная явно вторая строка эквивалентна

static int stats[256] = { 0 }; while (p<end) stats[*p++]++;

Итак, чтобы быть справедливым сравнением, вы должны сначала прочитать

 int stats[256] = { 0 }; while (p<end) stats[*p++]++;

Ваш компилятор может вывести гораздо больше, если у него есть переменные в известном состоянии.

Теперь может существовать время выполнения static, поскольку инициализация выполняется во время компиляции (или запуск программы).

Чтобы проверить, соответствует ли это вашей разнице, вы должны запускать одну и ту же функцию со статическим объявлением и циклом несколько раз, чтобы увидеть, исчезает ли разница, если число ваших призывов растет.

Но, как уже было сказано, лучше всего проверить ассемблер, который производит ваш компилятор, чтобы узнать, какая эффективная разница в коде, который производится.