TL : DR - dispatch_once
과 동일한 성능 특성을 갖는 스레드 안전 방식으로 C++ 11 정적 변수 초기화를 사용할 수 있습니다.,
class Object {
};
static Object *GetObjectCppStatic() {
static Object *object = new Object();
return object;
}
int main() {
GetObjectCppStatic();
}
가 인라인을 피하기 위해 clang++ test.cpp -O0 -fno-exceptions -S
(-O0
를 통해 어셈블리에이 컴파일, 동일한 일반적인 코드가 -Os
을 위해 제작 : 스테판 레 흐너의 대답에 따라
, 나는 C++ 정적 초기화 흐름을 테스트하는 가장 간단한 코드를 작성
__ZL18GetObjectCppStaticv: ## @_ZL18GetObjectCppStaticv
.cfi_startproc
## BB#0:
pushq %rbp
Lcfi6:
.cfi_def_cfa_offset 16
Lcfi7:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Lcfi8:
.cfi_def_cfa_register %rbp
cmpb $0, __ZGVZL18GetObjectCppStaticvE6object(%rip)
jne LBB2_3
## BB#1:
leaq __ZGVZL18GetObjectCppStaticvE6object(%rip), %rdi
callq ___cxa_guard_acquire
cmpl $0, %eax
je LBB2_3
## BB#2:
movl $1, %eax
movl %eax, %edi
callq __Znwm
leaq __ZGVZL18GetObjectCppStaticvE6object(%rip), %rdi
movq %rax, __ZZL18GetObjectCppStaticvE6object(%rip)
callq ___cxa_guard_release
LBB2_3:
movq __ZZL18GetObjectCppStaticvE6object(%rip), %rax
popq %rbp
retq
.cfi_endproc
우리는 확실히 ___cxa_guard_acquire
을 볼 수 -fno-exceptions
)는 생성 된 코드를 단순화하기 위해, GetObjectCppStatic
가 컴파일 보여줍니다 libC++ ABI here에 의해 구현되는 ___cxa_guard_release
. C++ 11을 사용한다는 것을 clang
으로 지정하지 않아도된다는 사실에 유의하십시오.
우리는 두 형식 모두 로컬 통계의 스레드 안전 초기화를 보장한다는 것을 알고 있습니다. 그러나 성능은 어떻습니까? 테스트 코드를 검사에게 무거운 경합 경합 (단일 스레드)와 두 방법과는 다음의 (멀티 스레드)
#include <cstdio>
#include <dispatch/dispatch.h>
#include <mach/mach_time.h>
class Object {
};
static double Measure(int times, void(^executionBlock)(), void(^finallyBlock)()) {
struct mach_timebase_info timebaseInfo;
mach_timebase_info(&timebaseInfo);
uint64_t start = mach_absolute_time();
for (int i = 0; i < times; ++i) {
executionBlock();
}
finallyBlock();
uint64_t end = mach_absolute_time();
uint64_t timeTook = end - start;
return ((double)timeTook * timebaseInfo.numer/timebaseInfo.denom)/
NSEC_PER_SEC;
}
static Object *GetObjectDispatchOnce() {
static Object *object;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
object = new Object();
});
return object;
}
static Object *GetObjectCppStatic() {
static Object *object = new Object();
return object;
}
int main() {
printf("Single thread statistics:\n");
printf("DispatchOnce took %g\n", Measure(10000000, ^{
GetObjectDispatchOnce();
}, ^{}));
printf("CppStatic took %g\n", Measure(10000000, ^{
GetObjectCppStatic();
}, ^{}));
printf("\n");
dispatch_queue_t queue = dispatch_queue_create("queue",
DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
printf("Multi thread statistics:\n");
printf("DispatchOnce took %g\n", Measure(1000000, ^{
dispatch_group_async(group, queue, ^{
GetObjectDispatchOnce();
});
}, ^{
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}));
printf("CppStatic took %g\n", Measure(1000000, ^{
dispatch_group_async(group, queue, ^{
GetObjectCppStatic();
});
}, ^{
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}));
}
64에서 다음과 같은 결과가 산출 :
Single thread statistics:
DispatchOnce took 0.025486
CppStatic took 0.0232348
Multi thread statistics:
DispatchOnce took 0.285058
CppStatic took 0.32596
따라서 측정 오차에 최대 , 두 방법의 성능 특성은 유사하며, 대부분이 두 가지 모두에 의해 수행되는 double-check locking 때문입니다. dispatch_once
의 경우, 이것은 _dispatch_once
기능에서 발생 다음 C++ 정적 초기화에 흐름
void
_dispatch_once(dispatch_once_t *predicate,
DISPATCH_NOESCAPE dispatch_block_t block)
{
if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
// ...
} else {
// ...
}
}
바로
___cxa_guard_acquire
호출하기 전에 발생합니다.
공식적인 언어 사양은 없지만 전혀 컴파일하지 않아도된다는 것은 유감입니다. -fthreadsafe-statics 옵션은 GCC/C++ 옵션 아래에 나열되어 있으며 clangs clang 및 clang ++ 드라이버에 의해 Target independent로 표시됩니다. –
나는 그것에 대해'__has_feature'을 찾을 수 없지만 https://stackoverflow.com/q/44500144/8918119는 clang에서도 사용할 수있는 안전한 지역 통계에 사용되는 특수 GCC 함수'__cxa_guard_acquire'를 언급합니다. –
clang 프리 프로세서가'__cpp_threadsafe_static_init'을 정의한 것처럼 보입니다. https://github.com/llvm-mirror/clang/blob/96c9689f478d292390b76efcea35d87cbad3f44d/lib/Frontend/InitPreprocessor.cpp#L504 –