Каково снижение производительности локальных переменных потока C++11 в GCC 4.8?
С проект на GCC 4.8 в changelog:
G++ теперь реализует C++11
thread_local
ключевое слово; это отличается от ГНУ__thread
ключевое слово в первую очередь в том, что она позволяет динамически семантика инициализации и разрушения. К сожалению, эта поддержка требуется штраф во время выполнения для ссылок на нефункциональные локальныеthread_local
переменные, даже если они не нуждаются в динамической инициализации, поэтому пользователи могут продолжать использовать__thread
для TLS переменные с семантика статической инициализации.
какова именно природа и происхождение этого штрафа во время выполнения?
очевидно, для поддержки не-функции-local thread_local
переменные там должна быть фаза инициализации потока перед входом в каждый поток main (так же, как есть статическая фаза инициализации для глобальных переменных), но они ссылаются на некоторые штрафы во время выполнения за это?
грубо говоря, что архитектура новая реализация GCC thread_local?
3 ответа:
(отказ от ответственности: я не знаю много о внутренностях GCC, так что это также образованная догадка.)
динамический
thread_local
инициализация добавлена в commit 462819c. Одним из изменений является:
* semantics.c (finish_id_expression): Replace use of thread_local
variable with a call to its wrapper.
таким образом, штраф за время выполнения заключается в том, что каждая ссылка
thread_local
переменная станет вызовом функции. Давайте проверим с помощью простого теста:// 3.cpp extern thread_local int tls; int main() { tls += 37; // line 6 tls &= 11; // line 7 tls ^= 3; // line 8 return 0; } // 4.cpp thread_local int tls = 42;
когда скомпилированный*, мы видим, что использование
tls
ссылка становится вызовом функции_ZTW3tls
, которые лениво инициализируют переменную один раз:00000000004005b0 <main>: main(): 4005b0: 55 push rbp 4005b1: 48 89 e5 mov rbp,rsp 4005b4: e8 26 00 00 00 call 4005df <_ZTW3tls> // line 6 4005b9: 8b 10 mov edx,DWORD PTR [rax] 4005bb: 83 c2 25 add edx,0x25 4005be: 89 10 mov DWORD PTR [rax],edx 4005c0: e8 1a 00 00 00 call 4005df <_ZTW3tls> // line 7 4005c5: 8b 10 mov edx,DWORD PTR [rax] 4005c7: 83 e2 0b and edx,0xb 4005ca: 89 10 mov DWORD PTR [rax],edx 4005cc: e8 0e 00 00 00 call 4005df <_ZTW3tls> // line 8 4005d1: 8b 10 mov edx,DWORD PTR [rax] 4005d3: 83 f2 03 xor edx,0x3 4005d6: 89 10 mov DWORD PTR [rax],edx 4005d8: b8 00 00 00 00 mov eax,0x0 // line 9 4005dd: 5d pop rbp 4005de: c3 ret 00000000004005df <_ZTW3tls>: _ZTW3tls(): 4005df: 55 push rbp 4005e0: 48 89 e5 mov rbp,rsp 4005e3: b8 00 00 00 00 mov eax,0x0 4005e8: 48 85 c0 test rax,rax 4005eb: 74 05 je 4005f2 <_ZTW3tls+0x13> 4005ed: e8 0e fa bf ff call 0 <tls> // initialize the TLS 4005f2: 64 48 8b 14 25 00 00 00 00 mov rdx,QWORD PTR fs:0x0 4005fb: 48 c7 c0 fc ff ff ff mov rax,0xfffffffffffffffc 400602: 48 01 d0 add rax,rdx 400605: 5d pop rbp 400606: c3 ret
сравните его с
__thread
версия, которая не будет иметь эту дополнительную обертку:00000000004005b0 <main>: main(): 4005b0: 55 push rbp 4005b1: 48 89 e5 mov rbp,rsp 4005b4: 48 c7 c0 fc ff ff ff mov rax,0xfffffffffffffffc // line 6 4005bb: 64 8b 00 mov eax,DWORD PTR fs:[rax] 4005be: 8d 50 25 lea edx,[rax+0x25] 4005c1: 48 c7 c0 fc ff ff ff mov rax,0xfffffffffffffffc 4005c8: 64 89 10 mov DWORD PTR fs:[rax],edx 4005cb: 48 c7 c0 fc ff ff ff mov rax,0xfffffffffffffffc // line 7 4005d2: 64 8b 00 mov eax,DWORD PTR fs:[rax] 4005d5: 89 c2 mov edx,eax 4005d7: 83 e2 0b and edx,0xb 4005da: 48 c7 c0 fc ff ff ff mov rax,0xfffffffffffffffc 4005e1: 64 89 10 mov DWORD PTR fs:[rax],edx 4005e4: 48 c7 c0 fc ff ff ff mov rax,0xfffffffffffffffc // line 8 4005eb: 64 8b 00 mov eax,DWORD PTR fs:[rax] 4005ee: 89 c2 mov edx,eax 4005f0: 83 f2 03 xor edx,0x3 4005f3: 48 c7 c0 fc ff ff ff mov rax,0xfffffffffffffffc 4005fa: 64 89 10 mov DWORD PTR fs:[rax],edx 4005fd: b8 00 00 00 00 mov eax,0x0 // line 9 400602: 5d pop rbp 400603: c3 ret
эта обертка не нужна в каждом случае использования
thread_local
хотя. Об этом можно узнать изdecl2.c
. Оболочка генерируется только тогда, когда:
это это не
Если переменная определена в текущем TU, то inliner позаботится о накладных расходах. Я ожидаю, что это будет верно для большинства применений thread_local.
для внешних переменных, если программист может быть уверен, что никакое использование переменной в не определяющем TU не должно инициализировать динамическую инициализацию (либо потому, что переменная статически инициализирована, либо использование переменной в определяющем TU будет выполнено до любого использования в другом TU), они могут избежать этих накладных расходов с опцией-fno-extern-tls-init.
C++11 thread_local имеет тот же эффект выполнения, что и спецификатор _ _ thread (
__thread
не является частью стандарта C;thread_local
является частью стандарта C++)это зависит от того, где переменная TLS (объявленная с
__thread
описатель) объявляется.
- если переменная TLS объявлена в исполняемом файле, то доступ быстрый
- если переменная TLS объявлена в коде общей библиотеки (скомпилирована с
-fPIC
опции компилятора) и-ftls-model=initial-exec
опции компилятора указывается, что доступ быстрый; однако применяется следующее ограничение: общая библиотека не может быть загружена через dlopen / dlsym (динамическая загрузка), единственный способ использования библиотеки-связать с ней во время компиляции (опция компоновщика-l<libraryname>
)- если переменная TLS объявлена в общей библиотеке (
-fPIC
compiler option set) тогда доступ очень медленный, так как предполагается общая динамическая модель TLS - здесь каждый доступ к переменной TLS приводит к вызову_tls_get_addr()
; это случай по умолчанию, потому что вы не ограничены в том, как используется общая библиотека.источники: обработка ELF для локального хранения потоков Ульрихом Дреппером https://www.akkadia.org/drepper/tls.pdf этот текст также содержит код, созданный для поддерживаемых целевых платформ.