Programing

GCC 4.8에서 C ++ 11 thread_local 변수의 성능 저하는 무엇입니까?

crosscheck 2020. 11. 22. 19:01
반응형

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. 래퍼는 다음 경우에만 생성됩니다.

  • 기능 국지 아니며 ,

    1. 그것이 extern(위의 예), 또는
    2. 유형에 사소하지 않은 소멸자가 있습니다 ( __thread변수에 허용되지 않음 ) 또는
    3. 유형 변수는 상수가 아닌 표현식 ( __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.

참고URL : https://stackoverflow.com/questions/13106049/what-is-the-performance-penalty-of-c11-thread-local-variables-in-gcc-4-8

반응형