Блог пользователя Edvard

Автор Edvard, 12 лет назад, По-русски

Добрый день!

Перед тем как задам вопрос напомню, что статические локальные переменные это практически глобальные переменные, только у них ограничена область видимости. Их использование очень удобно например потому, что многие переменные (особенно в олимпиадном программировании) хочется называть одинаково. Статические локальные переменные позволяют например в каждой функции объявить свой массив cur и с одной стороны это будет глобальная переменная (ей выделится из статическая память), а с другой стороны ее не будет видно вне функции.

Вопрос заключается в следующем: есть какие-нибудь существенные минусы static-переменных? Например медленная работа, медленное выделение памяти и т.д. Мне просто вспоминается как я однажды избавлялся от них из-за каких-то проблем и не могу вспомнить из-за каких.

Заранее спасибо всем кто отпишется.

P.S.: картиночку добавил, чтобы пост смотрелся лучше)

Upd: только, что обнаружил что если объявить большой массив static local, то код медленно компилится (g++). С global быстрее.

  • Проголосовать: нравится
  • +19
  • Проголосовать: не нравится

»
12 лет назад, # |
  Проголосовать: нравится +8 Проголосовать: не нравится

В шахматном программированию рекомендуют по возможности использовать static переменные, так как они работают быстрее. Из минусов — они статические, находятся в конкретном месте памяти (а не в стеке), поэтому при рекурсивных вызовах сохраняют значение. Медленней работать будет только если выгодней использовать эту переменную как регистровую, и компилятор это понимает.

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +27 Проголосовать: не нравится

    Это автосгенерированный ответ на вопрос?

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      В смысле? Этот ответ на вопрос про static локальные переменные. И кто постоянно минусует? Мне казалось что в приличном обществе давать советы можно независимо от цвета штанов. Видимо я ошибался, и пользователи фиолетового цвета права голоса не имеют. Не заслужили.

      • »
        »
        »
        »
        12 лет назад, # ^ |
        Rev. 5   Проголосовать: нравится +11 Проголосовать: не нравится

        Сорри, может это я совсем не в теме, но мне ваш ответ напомнил вот это: автоматически сгенерированный текст

        В шахматном программированию
        Что такое шахматное программирование?

        так как они работают быстрее
        За счёт чего они работают быстрее?

        поэтому при рекурсивных вызовах сохраняют значение
        Почему нас особо волнуют рекурсивные вызовы?

        Медленней работать будет только если выгодней использовать эту переменную как регистровую, и компилятор это понимает
        Что такое регистровая переменная? Когда их выгодней использовать? Почему когда компилятор что-то понимает, то становится медленней? И собственно что становится медленней?

        P.S. Автор топика задал очень интересный для меня вопрос и мне действительно хотелось бы в нём разобраться.

        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
            Проголосовать: нравится +23 Проголосовать: не нравится

          Что такое шахматное программирование? Шахматное программирование — написание шахматных программ, chess engine. Сила шахматной программы сильно зависит от производительности (оптимизаций). Вдобавок раньше прибавку силы от увеличения скорости в шахматных программах заметно преувеличивали, и компиляторы не умели так хорошо оптимизировать, видимо по-этому в советах по оптимизации кода шахматных программ советовали по возможности использовать static.

          За счёт чего они работают быстрее? Первая попавшаяся ссылка http://stackoverflow.com/questions/10525707/can-i-get-best-performance-making-static-variables ~~~~~ In typical implementations, the version with static will just put the string somewhere in memory at compile time, whereas the version without static will make the function (each time it's called) allocate some space on the stack and write the string into that space.

          The version with static, therefore,

          is likely to be quicker may use less memory will use less stack space (which on some systems is a scarce resource) will play nicer with the cache (which isn't likely to be a big deal for a small string, but might be if foo is something bigger). ~~~~~

          Почему нас особо волнуют рекурсивные вызовы?

          Чтоб не напортачить.

          Что такое регистровая переменная? Когда их выгодней использовать? Почему когда компилятор что-то понимает, то становится медленней? И собственно что становится медленней?

          Если компилятор видит static, то переменной (простых типов) будет выделено место в памяти, и оптимизация по хранению её в регистре проводиться не будет. Если компилятор тупой, то вероятность что он грамотно раскидает переменные по регистрам процессора весьма мала. Если умный — то соответственно без static возможностей по оптимизации у него больше.

          • »
            »
            »
            »
            »
            »
            12 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится

            На счет быстроты. Меня в первую очередь интересует сравнение времени работы static local vs global. Понятно, что static local работает быстрее, чем local.

            • »
              »
              »
              »
              »
              »
              »
              12 лет назад, # ^ |
              Rev. 2   Проголосовать: нравится +13 Проголосовать: не нравится

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

              Лучше думать так, без всяких ухищрений:

              • Мне нужна переменная в функции → определяю обычную переменную
              • Мне нужна переменная в функции, но она должна сохранять своё значение между вызовами → определяю static
              • Мне нужна переменная в функции, но она слишком большая для стека и ей разрешено сохранять своё значение между вызовами → определяю static

              EDIT: Пожалуй, стоит добавить и такие пункты:

              • Мне нужна константа в функции, и она — встроенного типа, например, int → определяю const¹
              • Мне нужна константа в функции, и она — нетривиального типа, например, массив или класс → определяю static const

              ¹ На самом деле, с современными компиляторами производительность не поменяется, даже если не написать const.

              • »
                »
                »
                »
                »
                »
                »
                »
                12 лет назад, # ^ |
                  Проголосовать: нравится 0 Проголосовать: не нравится

                Я именно так и понимаю. static local я хочу использовать как раз для больших массивов.

        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
          Rev. 5   Проголосовать: нравится -13 Проголосовать: не нравится

          осталось не ясным, почему в этом списке нет вопросов

          - что такое переменная?
          - что такое стек?
          - что такое компилятор?
          
»
12 лет назад, # |
Rev. 2   Проголосовать: нравится +58 Проголосовать: не нравится

Если определить глобальный статический и локальный статический массив из int, скомпилировать его с помощью GCC в Linux и посмотреть на сгенерированный ассемблерный код, то получаем следующее:

  • глобальный массив компилируется в
    .bss
a:
    .zero 40000000
  • локальный массив компилируется в
.local  _ZZ1fvE1b
.comm   _ZZ1fvE1b,40000000,32

Справка по директивам ассемблера здесь: http://sourceware.org/binutils/docs-2.23.1/as/Pseudo-Ops.html#Pseudo-Ops

Однако далее, в объектном файле, оба этих массива превращаются просто в области в секции .bss. Если же инициализировать массивы (arr = { 1, 2, 3}), то оба массива таким же одинаковым образом попадают в секцию .data, и оба занимают одинаковое количество места в объектном файле.

Получается, никаких различий между этими двумя массивами нет. В Windows/MinGW имеем то же самое, только вместо .local + .comm используется директива .lcomm.

Если же вместо массивов взять собственный класс с конструктором, то становится интереснее. Из ассемблерного кода видно, что для глобального статического объекта в секцию .init_array дописывается адрес конструктора, который, видимо, будет вызван один раз при запуске программы. А для локального статического объекта происходит следующее: при каждом вызове функции производится проверка, не был ли ещё объект инициализирован, и если нет, то вызывается конструктор. Причём проверка не совсем простая и включает в себя вызовы функций __cxa_guard_acquire и __cxa_guard_release.

То есть, для нетривиальных классов (non-POD) получается, что локальная статическая переменная замедляет выполнение программы по сравнению с глобальной статической переменной.

Вывод: использовать локальные статические переменные следует только тогда, когда их тип либо встроенный, например, int, либо POD-класс (то есть, как struct в C).

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Круто спасибо за объяснение. Только я не понял в связи с чем отличается время компиляции?

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      Скорей всего особенность компилятора. Локальные static от глобальных переменных практически ничем не отличаются. Кроме области видимости. Ну и разброс результатов. Возможно повторная компиляция покажет другой результат.

      • »
        »
        »
        »
        12 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится

        На массиве long double [3003][3003] время компиляции глобального массива меньше 1 секунды. У static local больше 9 секунд. Строчка компиляции:

        g++ -Wall -Wextra -Wconversion -static -fno-optimize-sibling-calls -fno-strict-aliasing -lm -s -x c++ -Wl,--stack=268435456 -O2 -o main main.cpp
        
        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
            Проголосовать: нравится +5 Проголосовать: не нравится

          Попробовал. Первая компиляция — 5s. Повторная 500 ms. Такая-же скорость как и глобальный массив. Попробуй скомпилировать повторно. У меня скорость компиляции плавает от 300 мс. до 5 секунд.

          • »
            »
            »
            »
            »
            »
            12 лет назад, # ^ |
            Rev. 2   Проголосовать: нравится +13 Проголосовать: не нравится

            Upd: ох ты кажется я понял в чем проблема. Ужас вообще exe-файл первого варианта весит 640Kb, второго 104Mb. И как тут можно говорить, что это почти одно и то же?????????? Может они работают одинаково, но это совсем разные вещи.

            Global. Стабильно меньше 1 секунды.

            #include <iostream>
            using namespace std;
            const int N = 3000 + 3;
            long double a[N][N];
            inline void solve()
            {
            	cout << a[0][0] << endl;
            }
            int main()
            {
            	solve();	
            	return 0;
            }
            

            Static local. Стабильно больше 9 секунд.

            #include <iostream>
            using namespace std;
            const int N = 3000 + 3;
            inline void solve()
            {
            	static long double a[N][N];
            	cout << a[0][0] << endl;
            }
            int main()
            {
            	solve();	
            	return 0;
            }
            
            • »
              »
              »
              »
              »
              »
              »
              12 лет назад, # ^ |
              Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится
              #include <cstdlib>
              #include <iostream>
              #include <iomanip>
              using namespace std;
              //long double a[3003][3003];
              void f()
              {  
                static long double a[3003][3003];
                 for (int k=0;k<100;k++)
                for (int i=0;i<3003;i++) for (int j=0;j<3003;j++) a[i][j]+=i+j;
                cout<<a[10][10];
              }
              int main(int argc, char** argv) {
                  f();
                  return 0;
              }
              

              52 кб, с -static 4Мб Просто чудеса :)

              • »
                »
                »
                »
                »
                »
                »
                »
                12 лет назад, # ^ |
                  Проголосовать: нравится +5 Проголосовать: не нравится

                Я кажется все понял. Это из-за того, что я написал inline. Я в ужасе inline всегда казался таким безобидным))

            • »
              »
              »
              »
              »
              »
              »
              12 лет назад, # ^ |
              Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

              Непонятно, с чем это связано, но… стоит убрать inline из solve(), и проблема исчезает! С GCC 4.8 на Linux повторить не удаётся (то есть, в обоих случаях работает правильно).

              В ассемблерном коде с inline видна директива .space 108216108, без inline массив вроде бы вообще удаляется оптимизатором.

              • »
                »
                »
                »
                »
                »
                »
                »
                12 лет назад, # ^ |
                Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

                Да я только, что это заметил. У меня просто с каких-то пор руки инстинктивно пишут inline. Очень странно.

                Upd: у меня g++ 4.6.2.

                • »
                  »
                  »
                  »
                  »
                  »
                  »
                  »
                  »
                  12 лет назад, # ^ |
                    Проголосовать: нравится +6 Проголосовать: не нравится

                  Кстати, я сейчас вспомнил, что у gen, тоже на MinGW, то ли раз, то ли два было что-то типа такого: программа с main() и solve() стабильно получает TLE. Берём и содержимое solve() вставляем в main(), solve() убираем — и… программа проходит.

                  С этими solve() действительно происходит что-то непонятное.

              • »
                »
                »
                »
                »
                »
                »
                »
                12 лет назад, # ^ |
                  Проголосовать: нравится 0 Проголосовать: не нравится

                В моем варианте точно не удаляется, скорость выполнения и компиляции — одинакова копейка-в-копейку.

                • »
                  »
                  »
                  »
                  »
                  »
                  »
                  »
                  »
                  12 лет назад, # ^ |
                  Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

                  Скорее — точно удаляется. (Надо компилировать с включённым -O2.)

                • »
                  »
                  »
                  »
                  »
                  »
                  »
                  »
                  »
                  12 лет назад, # ^ |
                  Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

                  5 секунд выполнения и 106.5 Мб выделенной памяти согласно диспетчера задач — значит массив точно есть. С включенным -O2

              • »
                »
                »
                »
                »
                »
                »
                »
                12 лет назад, # ^ |
                  Проголосовать: нравится 0 Проголосовать: не нравится

                Под Ubuntu с g++ 4.5.2 exe-файл маленький. Т.е. все норм.

                • »
                  »
                  »
                  »
                  »
                  »
                  »
                  »
                  »
                  12 лет назад, # ^ |
                    Проголосовать: нравится 0 Проголосовать: не нравится

                  Ну вот, так и оказалось — фича конкретного компилятора при некотором стечении обстоятельств.

»
12 лет назад, # |
  Проголосовать: нравится +20 Проголосовать: не нравится

Вот еще мнение Криса Касперски о локальных массивах. http://www.xakep.ru/magazine/xa/117/116/1.asp

foo()
{
int matrix[100][100]={{1,2,3},{4,5,6},{7,8,9}};
…
}

А здесь что не в порядке? Программист создает законный двухмерный массив, инициализируя малую его часть (очевидно, что остальные ячейки предполагается заполнить по ходу выполнения функции foo). Согласно Стандарту, здесь инициализируется весь массив, причем, ненулевые ячейки компиляторы инициализируют индивидуально, расходуя на каждую из них, по меньшей мере, одну машинную инструкцию. Это в идеале, а на практике компилятору MS VC необходимо 27 команд, чтобы справиться с вышеприведенным массивом. Хорошего мало, особенно, если функция foo вызывается больше одного раза. Стек не резиновый и обычно (читай — по умолчанию) потоку достается порядка 1 Мб.

За бездумное размещение массивов в стеке давно уже пора расстреливать. Ключевое слово "static", размещенное перед "int matrix", сокращает потребности в памяти и увеличивает скорость выполнения программы в несколько раз! А как быть, если статический массив нас «ну никак не устраивает»? Допустим, массив должен инициализироваться при каждом вхождении в функцию. Нет ничего проще! Размещаем исходный массив в глобальной или статической переменной, а при каждом вхождении в функцию копируем его во временный буфер, выделяемый из пула динамической памяти. Копирование, осуществляемое посредством memcpy, намного быстрее поэлементной инициализации (напоминаю, что статические массивы инициализируются на стадии компиляции, не транжиря процессорное время).

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +10 Проголосовать: не нравится

    Почему пост минусуют? Здесь что-то неверно написано?

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится +1 Проголосовать: не нравится

      Я наверху написал почему :) Мне нравится как пишет Касперски. Он и в асме силен, и отлично понимает слабые и сильные стороны компиляторов. И если Касперски говорит что локальные не static массивы это плохо, то скорей всего так оно и есть. У него только в конце статьи небольшая ошибка. Он хотел сделать массив на четыре элемента, а вышло у него на три.

      • »
        »
        »
        »
        12 лет назад, # ^ |
          Проголосовать: нравится +3 Проголосовать: не нравится

        У Касперского стиль изложение материала слегка понятней, последовательней и обоснованней чем у тебя :)

        Спасибо за подробное объяснение наверху!

        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
            Проголосовать: нравится 0 Проголосовать: не нравится

          В пол-второго ночи, после рабочего дня — тяжело последовательно излагать свои мысли )