TL; DR - C++ 11静的変数の初期化を、スレッドセーフで、dispatch_once
と同じパフォーマンス特性で使用することは可能です。インライン化を避けるためにclang++ test.cpp -O0 -fno-exceptions -S
(-O0
を経由してアセンブリにこれをコンパイル
class Object {
};
static Object *GetObjectCppStatic() {
static Object *object = new Object();
return object;
}
int main() {
GetObjectCppStatic();
}
、同じ一般的なコードは、-Os
のために生産された:ステファン・レヒナーの答えの後、私はC++の静的初期化フローをテストする最も簡単なコードを書いた
生成されたコードを簡素化する-fno-exceptions
)、GetObjectCppStatic
がにコンパイルされることを示しています。我々は間違いなく___cxa_guard_acquire
を見ることができます
__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
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);
}));
}
x64で以下の結果が得られる:
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とは独立しています。 –
私はそれに '__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 –