2017-11-30 13 views
4

C++ 11以降、ローカル変数staticは、-fno-threadsafe-staticsが指定されていない限り、スレッドセーフな方法で初期化されることが知られています(in this question)。Objective-C++でdispatch_onceを削除することはできますか?

+ (NSObject *)onlyOnce { 
    static NSObject *object; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
    object = [[NSObject alloc] init]; 
    }); 
    return object; 
} 

はるかに短いと交換することができる:

+ (NSObject *)onlyOnce { 
    static NSObject *object = [[NSObject alloc] init]; 
    return object; 
} 

C++ 11のC++言語の方言とのObjective-C++などのコードをコンパイルする場合、それは、以下のよく知られたパターンがあることを意味していより高い?

+0

言語の正式な仕様はありませんが、残念ながらコンパイルに依存する可能性があります。オプション-fthreadsafe-staticsは、GCC/C++オプションの下にリストされています。また、clangs clangとclang ++ドライバによってTargetとは独立しています。 –

+0

私はそれに '__has_feature'を見つけることができませんが、https://stackoverflow.com/q/44500144/8918119はclangでも利用可能な安全なローカル静的に使用される特別なGCC関数' __cxa_guard_acquire'について言及しています。 –

+1

clangプリプロセッサが '__cpp_threadsafe_static_init'を定義しているようですhttps://github.com/llvm-mirror/clang/blob/96c9689f478d292390b76efcea35d87cbad3f44d/lib/Frontend/InitPreprocessor.cpp#L504 –

答えて

0

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への呼び出しの前に発生します。

1

一般的にObjective-C-ObjectとObjective-C-ObjectsとC++オブジェクトとコードを混在させるObjective-C++は、「純粋な」C++ 11とは異なる言語です。したがって、Objective-C++の混在した世界では、C++ 11で保証されているすべてが自動的に保証されるとは思いません。そして、私は現在、Objective-C++で静的なローカル変数やブロック変数の特定の保証が与えられているかどうか、リンゴのドキュメントを調べています。

私はこのことには言及しませんでしたが、提案された "新しいスタイル"、すなわち静的なローカル変数を使用してオブジェクトを作成するという競合条件を導入しようとしました。 dispatch_onceと、同期を無視した1つの「実際の」競合状態「notOnlyOnce」(コードが実際に競合状態を導入していることを確かめる)。

「新しいスタイル」と「古いスタイル」の両方がスレッドセーフであるように見えますが、「notOnlyOnce」は明確ではありません。残念なことに、このようなテストでは、「新しいスタイル」が競合状態を生成することを単に証明することができますが、決して競合状態が存在しないことを証明することはできません。しかし、 "新しいスタイル"と "古いスタイル"は同じように動作しますが、 "notOnlyOnce"は同じ設定で競合条件を表示するため、静的なローカル変数は、あなたが提案したとおりに動作すると見なすことができます。

次のコードとそれぞれの出力を参照してください。

@interface SingletonClass : NSObject 

- (instancetype)init; 

@end 

@implementation SingletonClass 

- (instancetype)init { 
    self = [super init]; 
    std::cout << "Created a singleton object" << std::endl; 
    for (int i=0; i<1000000; i++) { i++; } 
    return self; 
} 

@end 

@interface TestClassObjCPP : NSObject 

@property (nonatomic) SingletonClass *sc; 

+ (SingletonClass *)onlyOnceNewStyle; 
+ (SingletonClass *)onlyOnceOldStyle: (TestClassObjCPP*)caller; 
+ (SingletonClass *)notOnlyOnce: (TestClassObjCPP*)caller; 

@end 

@implementation TestClassObjCPP 


+ (SingletonClass *)onlyOnceNewStyle { 
    static SingletonClass *object = [[SingletonClass alloc] init]; 
    return object; 
} 

+ (SingletonClass *)onlyOnceOldStyle: (TestClassObjCPP*)caller { 

    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     caller.sc = [[SingletonClass alloc] init]; 
    }); 

    return caller.sc; 
} 

+ (SingletonClass *)notOnlyOnce: (TestClassObjCPP*)caller { 

    if (caller.sc == nil) 
     caller.sc = [[SingletonClass alloc] init]; 

    return caller.sc; 
} 

@end 


int main(int argc, char * argv[]) { 


    @autoreleasepool { 

     std::cout << "Before loop requesting singleton." << std::endl; 
     TestClassObjCPP *caller = [[TestClassObjCPP alloc] init]; 
     caller.sc = nil; 
     for (int i=0; i<10000; i++) { 
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
       [TestClassObjCPP onlyOnceNewStyle]; // (1) 
       // [TestClassObjCPP onlyOnceOldStyle:caller]; // (2) 
       // [TestClassObjCPP notOnlyOnce:caller]; // (3) 
      }); 

     } 
     std::cout << "After loop requesting singleton." << std::endl; 

     return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 
    } 
} 

onlyOnceNewStyle用の出力(1):

Before loop requesting singleton. 
Created a singleton object 
After loop requesting singleton. 

onlyOnceOldStyle用の出力(2):notOnlyOnceため

Before loop requesting singleton. 
Created a singleton object 
After loop requesting singleton. 

出力(3):

Before loop requesting singleton. 
Created a singleton object 
Created a singleton object 
Created a singleton object 
After loop requesting singleton. 

だから明確なはい、いいえではありませんが、私はそれを願っています何らかの形で役立ちます。

+0

多分、あなたは 'onlyOnceNewStyle'に' static SingletonClass * object'を持っていたかったでしょうか? –

+0

@A.A:oops、yes。異なるものをテストしたバージョンをコピーしました。しかし出力は静的なバージョンからのものです(そうでなければ何千もの "オブジェクトが作成された"ラインが出力されます)。 –

関連する問題