Оптимизация инициализации статических переменных внутри функции

Есть функция:

int foo()
{
    static int static_var1;
    static int static_var2 = static_cast<int>( std::time(nullptr) );

    //некоторый код...
}

Инициализация статических переменных является потокобезопасной. Если вызвать функцию foo() сразу из нескольких потоков, то какой-то поток инициализирует переменную static_var1, если до этого она ещё ни разу не была инициализированна, а другие потоки при этом любезно подождут окончания инициализации, затем продолжат свою работу.
Аналогичная история произойдёт и с переменной static_var2.

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

Тем не менее, что касается переменной static_var1, то тут явно напрашивается следующая оптимизация: инициализируем её нулём в главном потоке до первого вызова функции foo(), весь код синхронизации/инициализации static_var1 внутри foo() выбрасываем.

Мои вопросы таковы: 1) Делают ли компиляторы такую оптимизацию? 2) Как изменится ответ на предыдущий вопрос, если функцию foo() сделать шаблонной?


Ответы (1 шт):

Автор решения: AlexGlebe
// g++ -g -Wall -Wextra -Wpedantic -std=c++11 -Os staticmem.cpp -fverbose-asm -S
# include <ctime>

int foo()
{
    static int static_var1;
    static int static_var2 = static_cast<int>( std::time(nullptr) );

    //некоторый код...
    return 0 ;
}

В вашем первом варианте действительно идёт проверка на инициализирование. Закрывается мютекс идёт проверка и вызов time если надо, потом открывается мютекс.

    movb    _ZGVZ3foovE11static_var2(%rip), %al #, _1
    testb   %al, %al    # _1
    jne .L9 #,
    subq    $8, %rsp    #,
    movl    $_ZGVZ3foovE11static_var2, %edi #,
    call    __cxa_guard_acquire #
.LVL0:
    testl   %eax, %eax  # _2
    je  .L3 #,
    xorl    %edi, %edi  #
    call    time    #
.LVL1:
    movl    $_ZGVZ3foovE11static_var2, %edi #,
    call    __cxa_guard_release #
.LVL2:
.L3:
    .loc 1 11 0 is_stmt 1
    xorl    %eax, %eax  #
    popq    %rdx    #
    ret
.L9:
    xorl    %eax, %eax  #
    ret

Можно избавиться от задержки потоков если проинициализировать сразу переменные при старте программы. Это код при старте без мютексов.

namespace nsfoo2 {
  int static_var1;
  int static_var2 = static_cast<int>( std::time(nullptr) );
}

int foo2()
{
    //некоторый код...
  nsfoo2::static_var1 = .. ;
  nsfoo2::static_var2 = .. ;
  return 0 ;
}

asm :

_GLOBAL__sub_I__Z3foov:
    subq    $8, %rsp    #,
    xorl    %edi, %edi  #
    call    time    #
    movl    %eax, _ZN6nsfoo211static_var2E(%rip)    # _3, static_var2
    popq    %rax    #
    ret
_ZN6nsfoo211static_var2E:
    .zero   4
    .globl  _ZN6nsfoo211static_var1E
    .align 4
    .type   _ZN6nsfoo211static_var1E, @object
    .size   _ZN6nsfoo211static_var1E, 4
_ZN6nsfoo211static_var1E:
    .zero   4

Как видно блокировки нет. А глобальный переменные var1 и var2 при старте сразу же имеют нулевое значение.

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

→ Ссылка