2017-11-30 22 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와 목표 - C++로 코드를 컴파일 ++ 언어의 방언 C++ (11) 그리고 더 높은?

+0

공식적인 언어 사양은 없지만 전혀 컴파일하지 않아도된다는 것은 유감입니다. -fthreadsafe-statics 옵션은 GCC/C++ 옵션 아래에 나열되어 있으며 clangs clang 및 clang ++ 드라이버에 의해 Target independent로 표시됩니다. –

+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 - 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 호출하기 전에 발생합니다.

1

일반적으로 Objective-C-Object와 C++ 객체 및 코드를 혼합 할 수있는 Objective-C++은 "순수한"C++ 11과 다른 언어입니다. 따라서 Objective-C++의 혼합 된 세계에서는 C++ 11에 대해 보장 된 모든 것이 자동으로 보장되지 않는다고 생각합니다. 그리고 저는 정적 인 지역 변수 또는 블록 변수에 대한 확실한 보장이 Objective-C++에서 제공되는지 여부에 관계없이 사과의 문서를 조사하는 데 시간을 할애하고 있습니다.

나는 이것에 대한 진술을 찾지 못했기 때문에 객체의 생성에 경쟁 조건을 도입하려고 시도했다. 하나는 제안 된 "새로운 스타일", 즉 정적 지역 변수를 사용하고 하나는 "예전 스타일" dispatch_once으로, 그리고 하나의 "실제"경쟁 조건 "notOnlyOnce"는 동기화를 무시합니다 (코드가 실제로 경쟁 조건을 도입하는지 확인하기 위해).

"새로운 스타일"과 "이전 스타일"은 스레드 안전성이있는 반면 "notOnlyOnce"는 명확하지 않은 것으로 나타났습니다. 불행하게도, 그러한 테스트는 "새로운 스타일"이 경쟁 조건을 생성한다는 것을 증명할 수는 있지만 경쟁 조건이 결코 존재하지 않는다는 것을 증명할 수는 없습니다. 그러나 "새로운 스타일"과 "이전 스타일"은 동일하게 동작하지만 "notOnlyOnce"는 동일한 설정에서 경쟁 조건을 표시하므로 적어도 정적 로컬 변수는 사용자가 제안한대로 작동한다고 가정 할 수 있습니다.

다음 코드와 해당 출력을 참조하십시오. onlyOnceNewStyle위한

@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])); 
    } 
} 

출력 (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 : 죄송합니다. 내가 뭔가 다른 것을 테스트 한 버전을 복사했습니다. 그러나 출력은'static '을 가진 버전에서 나온 것입니다. 그렇지 않으면 수천 개의 "객체 생성"라인이 출력되었을 것입니다. –