Что здесь делает gcc, чтобы запустить этот код один раз для каждого потока? - PullRequest
0 голосов
/ 19 января 2019

Я только что наткнулся на эту технику для запуска кода один раз для потока.Я не знаю, как это работает на самом низком уровне, хотя.Особенно, на что указывает fs?Что означает .zero 8?Есть ли причина, по которой идентификатор равен @tpoff?

int foo();

void bar()
{
    thread_local static auto _ = foo();
}

Вывод (с -O2):

bar():
        cmp     BYTE PTR fs:guard variable for bar()::_@tpoff, 0
        je      .L8
        ret
.L8:
        sub     rsp, 8
        call    foo()
        mov     BYTE PTR fs:guard variable for bar()::_@tpoff, 1
        add     rsp, 8
        ret
guard variable for bar()::_:
        .zero   8

1 Ответ

0 голосов
/ 19 января 2019

Сегментная база fs - это адрес локального хранилища потоков (по крайней мере в Linux x86-64).

.zero 8 резервирует 8 байтов нулей (предположительно в BSS).Обратитесь к руководству по GAS: https://sourceware.org/binutils/docs/as/Zero.html, ссылки в https://stackoverflow.com/tags/x86/info.

@tpoff предположительно означают, что адрес относится к локальному хранилищу потока, вероятно, обозначает смещение потока, я не знаю.


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

1-байтовая защитная переменная находится в локальном хранилище потока.Фактический _ сам по себе оптимизирован, потому что он никогда не читается. Обратите внимание, что нет места для eax после возврата foo.

Кстати, _ - странный (плохой) выбор дляимя переменной.Легко пропустить его и, вероятно, зарезервировано для использования реализацией.


Здесь есть хорошая оптимизация: обычно (для не-локальных потоков static int var = foo();), если он находит переменную защиты isn 't уже инициализирован, ему нужен потокобезопасный способ убедиться, что только один поток на самом деле выполняет инициализацию (по сути, берет блокировку).

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

(извините за краткий ответ, я могупозже рассмотрим пример с https://godbolt.org/ локальной нить-нити static и найдите SO Q & A.)

...