GCC 4.8에서 C ++ 11 thread_local 변수의 성능 저하는 무엇입니까?
로부터 GCC 4.8 초안 변경 내역 :
G ++는 이제 C ++ 11
thread_local
키워드를 구현합니다 . 이것은__thread
주로 동적 초기화 및 소멸 의미론을 허용한다는 점에서 GNU 키워드 와 다릅니다 . 안타깝게도이 지원에는thread_local
동적 초기화가 필요하지 않더라도 비 함수 로컬 변수에 대한 참조에 대한 런타임 패널티 가 필요하므로 사용자는__thread
정적 초기화 의미 체계가있는 TLS 변수 를 계속 사용할 수 있습니다 .
이 런타임 패널티의 본질과 기원은 정확히 무엇입니까?
분명히 비 함수 로컬 thread_local
변수 를 지원 하려면 모든 스레드 메인에 대한 항목 이전에 스레드 초기화 단계가 있어야합니다 (글로벌 변수에 대한 정적 초기화 단계가있는 것처럼),하지만 그 이상의 런타임 패널티를 언급하고 있습니까? ?
대략적으로 말하자면 gcc의 새로운 thread_local 구현의 아키텍처는 무엇입니까?
(면책 조항 : GCC의 내부에 대해 많이 알지 못하기 때문에 이것은 교육적인 추측이기도합니다.)
동적 thread_local
초기화는 커밋 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
. 래퍼는 다음 경우에만 생성됩니다.
기능 국지 가 아니며 ,
- 그것이
extern
(위의 예), 또는 - 유형에 사소하지 않은 소멸자가 있습니다 (
__thread
변수에 허용되지 않음 ) 또는 - 유형 변수는 상수가 아닌 표현식 (
__thread
변수 에도 허용되지 않음)에 의해 초기화됩니다 .
- 그것이
In all other use cases, it behaves the same as __thread
. That means, unless you have some extern __thread
variables, you could replace all __thread
by thread_local
without any loss of performance.
*: I compiled with -O0 because the inliner will make the function boundary less visible. Even if we turn up to -O3 those initialization checks still remain.
If the variable is defined in the current TU, the inliner will take care of the overhead. I expect that this will be true of most uses of thread_local.
For extern variables, if the programmer can be sure that no use of the variable in a non-defining TU needs to trigger dynamic initialization (either because the variable is statically initialized, or a use of the variable in the defining TU will be executed before any uses in another TU), they can avoid this overhead with the -fno-extern-tls-init option.
C++11 thread_local has the same runtime effect as the __thread specifier (__thread
is not part of the C standard; thread_local
is part of the C++ standard)
it depends where the TLS variable (declared with __thread
specifier) is declared.
- if TLS variable is declared in an executable then access is fast
- if TLS variable is declared within shared library code (compiled with
-fPIC
compiler option) and-ftls-model=initial-exec
compiler option is specified then access is fast; however the following limitation applies: the shared library can't be loaded via dlopen/dlsym (dynamic loading), the only way of using the library is to link with it during compilation (linker option-l<libraryname>
) - if TLS variable is declared within a shared library (
-fPIC
compiler option set) then access is very slow, as the general dynamic TLS model is assumed - here each access to a TLS variable results in a call to_tls_get_addr()
; this is the default case because you are not limited in the way that the shared library is used.
Sources: ELF Handling For Thread-Local Storage by Ulrich Drepper https://www.akkadia.org/drepper/tls.pdf this text also lists the code that is generated for the supported target platforms.
'Programing' 카테고리의 다른 글
github README.md에서 HTML 콘텐츠를 표시하는 방법은 무엇입니까? (0) | 2020.11.22 |
---|---|
python argparse를 사용하여 여러 중첩 하위 명령을 구문 분석하는 방법은 무엇입니까? (0) | 2020.11.22 |
Vundle과 NeoBundle의 차이점은 무엇입니까? (0) | 2020.11.22 |
strncpy가 안전하지 않은 이유는 무엇입니까? (0) | 2020.11.22 |
Django는 어떤 용도로 사용됩니까? (0) | 2020.11.22 |