Оптимизация инициализации статических переменных внутри функции
Есть функция:
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 шт):
// 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 при старте сразу же имеют нулевое значение.
Шаблонные переменные или нет не имеет значение. Если в вашем коде важен момент инициализации тогда делайте это внутри функции. Если не важно в какой момент это сделать то делайте это при старте программы.