是否可以在Objective-C ++中删除dispatch_once? [英] Is it possible to remove dispatch_once in Objective-C++?

查看:79
本文介绍了是否可以在Objective-C ++中删除dispatch_once?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

自C ++ 11起,已知本地static以线程安全的方式初始化(​​除非给出了-fno-threadsafe-statics),如指定的

Since C++11, local static variables are known to be initialized in a thread safe manner (unless the -fno-threadsafe-statics is given), as specified in this question. Does that mean that the following well-known pattern:

+ (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 ++时?

When compiling the code as Objective-C++ with C++ language dialect of C++11 and higher?

推荐答案

TL; DR -似乎可以以线程安全的方式使用C ++ 11静态变量初始化,并且具有与dispatch_once具有相同的性能特征.

TL;DR - it seems that it's possible to use C++11 static variable initialization in a thread safe manner which has the same performance characteristics as dispatch_once.

根据Stephan Lechner的回答,我编写了最简单的代码来测试C ++静态初始化流程:

Following Stephan Lechner's answer, I wrote the most simple code that tests the C++ static initialization flow:

class Object {  
};

static Object *GetObjectCppStatic() {
  static Object *object = new Object();
  return object;
}

int main() {
  GetObjectCppStatic();
}

通过clang++ test.cpp -O0 -fno-exceptions -S将其编译为汇编语言(-O0为避免内联,为-Os生成了相同的通用代码,为简化生成的代码而为-fno-exceptions生成了相同的通用代码),表明GetObjectCppStatic编译为:

Compiling this to assembly via clang++ test.cpp -O0 -fno-exceptions -S (-O0 to avoid inlining, same general code is produced for -Os, -fno-exceptions to simplify generated code), shows that GetObjectCppStatic compiles to:

__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实现的___cxa_guard_acquire___cxa_guard_release 此处.请注意,我们甚至不必为clang指定使用C ++ 11,因为默认情况下甚至早于默认支持.

We can definitely see the ___cxa_guard_acquire and ___cxa_guard_release, implemented by the libc++ ABI here. Note that we didn't even had to specify to clang that we use C++11, as apparently this was supported by default even prior than that.

因此,我们知道这两种形式都确保对线程进行局部安全的初始化.但是性能如何呢?以下测试代码检查无争用(单线程)和重争用(多线程)的两种方法:

So we know both forms ensures thread-safe initialization of local statics. But what about performance? The following test code checks both methods with no contention (single threaded) and with heavy contention (multi threaded):

#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上产生以下结果:

Which yields the following results on x64:

Single thread statistics:
DispatchOnce took 0.025486
CppStatic took 0.0232348

Multi thread statistics:
DispatchOnce took 0.285058
CppStatic took 0.32596

直到出现测量错误,这两种方法的性能特征似乎都是相似的,这主要是由于

So up to measurement error, it seems that the performance characteristics of both methods are similar, mostly due to the double-check locking that is performed by both of them. For dispatch_once, this happens in the _dispatch_once function:

void
_dispatch_once(dispatch_once_t *predicate,
    DISPATCH_NOESCAPE dispatch_block_t block)
{
  if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
    // ...
  } else {
    // ...
  }
}

在C ++静态初始化流程中,它恰好在调用___cxa_guard_acquire之前发生.

Where in the C++ static initialization flow it happens right before the call to ___cxa_guard_acquire.

这篇关于是否可以在Objective-C ++中删除dispatch_once?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆