2016-10-27 8 views
4

thread_local에 의존하는 타사 라이브러리를 사용하고 있습니다. 결과적으로 thread_local 변수가 무조건 동일한 함수 내에서 이전에 다른 호출에 의해 초기화되었다는 사실에도 불구하고 일부 사이클의 각 반복에서 (심지어는 모두 체크하지 못했습니다) 내 프로그램에서 __tls_init()을 반복적으로 호출합니다. 전체 프로그램 시작). 내 x86_64__tls_init()에서__tls_init 호출을 줄이거 나 제거하는 방법은 무엇입니까?

첫 번째 지침은 그래서 이것은 각 스레드 당이라고 처음으로, %fs:[email protected]에서 값이 1로 설정되어

cmpb $0, %fs:[email protected] 
je  .L530 
ret 
.L530: 
pushq %rbp 
pushq %rbx 
subq (some stack space), %rsp 
movb $1, %fs:[email protected] 

하고 더 호출은 즉시 반환. 그러나 여전히 thread_local 변수에 액세스 할 때마다 call의 모든 오버 헤드를 의미합니다.

정적으로 링크 된 (실제로 생성 된!) 함수이므로 컴파일러는이 조건으로 시작한다는 것을 "알고"있으며이 함수를 더 많이 호출 할 필요가 없다는 것을 흐름 분석에서 알 수 있습니다 한번 이상. 그러나 그렇지 않습니다.

불필요한 call __tls_init 명령어를 없애거나 적어도 컴파일러가 시간이 중요한 섹션에서이를 방출하지 않도록 할 수 있습니까? 실제 컴파일에서

예 상황 : (-03)

pushq %r13 
movq %rdi, %r13 
pushq %r12 
pushq %rbp 
pushq %rbx 
movq %rsi, %rbx 
subq $88, %rsp 
call __tls_init    // always gets called 
movq (%rbx), %rdi 
call <some local function> 
movq 8(%rax), %r12 
subq (%rax), %r12 
movq %rax, %rbp 
sarq $4, %r12 
cmpq $1, %r12 
jbe .L6512 
leaq -2(%r12), %rax 
movq $0, (%rsp) 
leaq 48(%rsp), %rbx 
movq %rax, 8(%rsp) 
.L6506: 
call __tls_init    // needless and called potentially very many times! 
movq %rsp, %rsi 
movq %rsp, %rdi 
addq $8, %rbx 
call <some other local function> 
movq %rax, -8(%rbx) 
leaq 80(%rsp), %rax 
cmpq %rbx, %rax 
jne .L6506      // cycle 

업데이트 : 위의 소스 코드는 지나치게 복잡하다. 이 컴파일러 탐색기에서 최대 최적화 설정 (link to this particular example) 분석을 참조하면

void external(int); 

struct X { 
    volatile int a; // to prevent optimizing to a constexpr 
    X() { a = 5; } // to enforce calling a c-tor for thread_local 
    void f() { external(a); } // to prevent disregarding the value of a 
}; 

thread_local X x; 

void f() { 
    x.f(); 
    for(int j = 0; j < 10; j++) 
    x.f(); // x is totally initialized now 
} 

, 당신은 중복 장착 한 후 루프 의 모든 반복에 0에 대한 fs:[email protected]을 확인하는 동일한 현상을 알 수 있습니다 : 여기 MWE입니다 a __tls_init이이 매우 단순한 경우에 인라이 율화 되더라도, 이 인 레이블, 즉 .L4에 있습니다 (출력은 동일하게 유지된다고 가정).

이 질문은 G ++에 관한 것이지만 CLang (see in Compiler Explorer)은이 점을 더욱 분명하게 보여줍니다.

외부 함수 호출이이 예제에서 저장된 값을 덮어 쓸 수 있다고 말할 수 있습니다. 그러나 무엇이 보증 될 것입니까? 그렇다면 호출 규칙을 무시할 수도 있습니다. 이러한 점에서 컴파일러는 단지 그것이 훌륭하게 작동 할 것이라고 가정해야합니다. 게다가 위의 메인 코드에는 외부 함수가 없었고 단일 번역 단위가있었습니다. MWE와 같은 작은 예제에서는 컴파일러 을 감지하고 제거하여 어떻게 든 가능해야 함을 나타냅니다. .

+0

당신은을 보여 주어야을 C++의 관련 소스 코드 –

+0

@BasileStarynkevitch 추가되었습니다. –

답변

3

거기 TLS 호출을 제거하기 위해 어떤 컴파일러 옵션이지만, 특정 코드가 함수에서 TLS 객체에 대한 포인터를 사용하여 최적화 할 수 있다면 나도 몰라 :

void f() { 
    auto ptr = &x; 
    ptr->f(); 
    for(int j = 0; j < 10; j++) 
    ptr->f(); 
} 
+1

이제 우아합니다 (더 작은 변화 인 참조를받는 것만으로도 효과가 있습니다). 필자는 주 프로그램에서이 변경 작업을 확실히 수행 할 수 있으며 구현하기가 쉽기 때문에 라이브러리 작성자에게도 확신을 줄 수 있습니다. 내 현실 세계에서 어떻게 작동하는지 시험해 보겠습니다. 아이디어를 가져 주셔서 감사합니다! –