Статическая постоянная в сравнении с постоянной в функции, которая вызывается повторно - PullRequest
30 голосов
/ 22 августа 2009

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

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

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

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

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

Ответы [ 8 ]

51 голосов
/ 22 августа 2009

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Скомпилированный код сборки с использованием 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
2 голосов
/ 22 августа 2009

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

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

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

2 голосов
/ 22 августа 2009

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

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

1 голос
/ 22 августа 2009

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

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

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

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

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

0 голосов
/ 28 июня 2015

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

0 голосов
/ 22 августа 2009

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

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

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

0 голосов
/ 22 августа 2009

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

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