Подтвердить что ты не робот

Статическая константа в зависимости от константы в функции, которая вызывается многократно

Мне просто интересно, как

void test()
{
   const static int ABC = 12;
   cout << ABC;
}

отличается от

void test()
{
   const int ABC = 12;
   cout << ABC;
}

если эта функция неоднократно вызывается во время выполнения программы? Я имею в виду, есть ли разница в производительности? Или есть причина, почему вы должны предпочесть один за другим?

4b9b3361

Ответ 1

Есть несколько вещей, которые влияют на ответ:

  • Во-первых, до тех пор, пока значение const, оно почти наверняка будет оптимизировано в любом случае. Это означает, что полученный код, скорее всего, будет таким же.
  • Во-вторых, члены static хранятся в другом месте, что означает меньшую локальность и, возможно, пропущен кеш.
  • В-третьих, стоимость инициализации зависит от типа. В вашем случае для int стоимость инициализации в основном отсутствует. Для более сложных пользовательских типов это может быть огромным.

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

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

Если тип дорогой для построения, вы можете использовать static.

И, конечно, последнее и самое главное:

Не доверяйте нашим догадкам. Если вас беспокоит производительность, есть только один правильный ход действий:

  • Измерьте это, чтобы убедиться, что это действительно проблема.
  • Измерьте производительность каждого возможного решения.
  • Выберите решение, обеспечивающее максимальную производительность.

Ответ 2

Хорошо теоретически первый метод немного лучше, поскольку он только наследует переменную один раз.

Однако компилятор просто удалит строку "const" и заменит "cout < 12;" в функцию (при компиляции с оптимизацией, очевидно).

Ответ 3

В первом броске ABC будет инициализироваться только один раз, при первом вызове функции. Во втором случае ABC будет инициализироваться каждый раз. Вы почувствуете разницу, если ABC сложный тип с конструктором. Он может выделять память или инициализировать некоторые мьютекс. Для int нет никакой разницы на практике.

Согласно С++ 03 Стандарт 3.7.1/2:

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

Ответ 4

Это зависит от компилятора.

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

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

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

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

Ответ 5

Вы также можете получить представление о том, что сделает компилятор, используя ваш ассемблер компилятора или что-то вроде этой онлайн-утилиты: https://godbolt.org и посмотрим на полученный код сборки.

Это стоило посмотреть, так как вопрос фундаментален.

Пример, основанный на вашем вопросе, рассматривающий примитивы vs "complex" (даже не очень) с учетом локального и статического тикового объявления и экземпляра:

#include <iostream>

struct non_primitive
{
    int v;
    non_primitive(const int v_) : v(v_) {}
};

void test_non_static_const_primitive()
{
   const int ABC = 12;
   std::cout << ABC;
}

void test_static_const_primitive()
{
   const static int ABC = 12;
   std::cout << ABC;
}

void test_non_static_constant_non_primitive_global_struct() 
{
    const non_primitive s(12);
    std::cout << s.v;
}

void test_non_static_constant_non_primitive_local_struct() 
{
    struct local_non_primitive
    {
        int v;
        local_non_primitive(const int v_) : v(v_) {}
    };
    const local_non_primitive s(12);
    std::cout << s.v;
}

void test_static_constant_non_primitive_global_struct() 
{
    const static non_primitive s(12);
    std::cout << s.v;
}

void test_static_constant_non_primitive_local_struct() 
{
    struct local_non_primitive
    {
        int v;
        local_non_primitive(const int v_) : v(v_) {}
    };
    const static local_non_primitive s(12);
    std::cout << s.v;
}

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

Полученная разница в стоимости относительно велика.

Скомпилированный ассемблерный код с использованием gcc7.2 с оптимизацией -O3:

test_non_static_const_primitive():
  mov esi, 12
  mov edi, OFFSET FLAT:std::cout
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_static_const_primitive():
  mov esi, 12
  mov edi, OFFSET FLAT:std::cout
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_non_static_constant_non_primitive_global_struct():
  mov esi, 12
  mov edi, OFFSET FLAT:std::cout
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_non_static_constant_non_primitive_local_struct():
  mov esi, 12
  mov edi, OFFSET FLAT:std::cout
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_static_constant_non_primitive_global_struct():
  movzx eax, BYTE PTR guard variable for test_static_constant_non_primitive_global_struct()::s[rip]
  test al, al
  je .L7
  mov esi, DWORD PTR test_static_constant_non_primitive_global_struct()::s[rip]
  mov edi, OFFSET FLAT:std::cout
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
.L7:
  sub rsp, 8
  mov edi, OFFSET FLAT:guard variable for test_static_constant_non_primitive_global_struct()::s
  call __cxa_guard_acquire
  test eax, eax
  mov esi, DWORD PTR test_static_constant_non_primitive_global_struct()::s[rip]
  je .L8
  mov edi, OFFSET FLAT:guard variable for test_static_constant_non_primitive_global_struct()::s
  mov DWORD PTR test_static_constant_non_primitive_global_struct()::s[rip], 12
  call __cxa_guard_release
  mov esi, 12
.L8:
  mov edi, OFFSET FLAT:std::cout
  add rsp, 8
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_static_constant_non_primitive_local_struct():
  movzx eax, BYTE PTR guard variable for test_static_constant_non_primitive_local_struct()::s[rip]
  test al, al
  je .L14
  mov esi, DWORD PTR test_static_constant_non_primitive_local_struct()::s[rip]
  mov edi, OFFSET FLAT:std::cout
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
.L14:
  sub rsp, 8
  mov edi, OFFSET FLAT:guard variable for test_static_constant_non_primitive_local_struct()::s
  call __cxa_guard_acquire
  test eax, eax
  mov esi, DWORD PTR test_static_constant_non_primitive_local_struct()::s[rip]
  je .L15
  mov edi, OFFSET FLAT:guard variable for test_static_constant_non_primitive_local_struct()::s
  mov DWORD PTR test_static_constant_non_primitive_local_struct()::s[rip], 12
  call __cxa_guard_release
  mov esi, 12
.L15:
  mov edi, OFFSET FLAT:std::cout
  add rsp, 8
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
_GLOBAL__sub_I__Z31test_non_static_const_primitivev:
  sub rsp, 8
  mov edi, OFFSET FLAT:std::__ioinit
  call std::ios_base::Init::Init()
  mov edx, OFFSET FLAT:__dso_handle
  mov esi, OFFSET FLAT:std::__ioinit
  mov edi, OFFSET FLAT:std::ios_base::Init::~Init()
  add rsp, 8
  jmp __cxa_atexit

Ответ 6

Для базового типа, такого как целочисленное значение, тогда я бы проглотил использование статического как "преждевременную оптимизацию", если вы не сделали какой-то бенчмаркинг и не приняли во внимание различные компромиссы (например, инициализацию статического ненулевое значение часто требует, чтобы запись в таблице указывала положение, размер и начальное значение, которые должны быть установлены при загрузке кода).

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

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

Ответ 7

Я бы пошел со вторым - это более читаемо. почему необязательно добавить ключевое слово (статическое), которое на самом деле не добавляет никакого значения кому-либо, читающему код.

Ответ 8

Статический пример константы, безусловно, сохраняет время выполнения для последующих вызовов, намного быстрее, чем сложная конструкция объекта использует статический const, но я сомневаюсь в необходимости ограничить область действия ABC и ввести вариации в поведении функции от 1-го вызовите следующие вызовы. Обычно файл содержит связанные функционально связанные функции, просто дайте объем файла ABC и сделайте с ним.