|
|
51CTO旗下网站
|
|
移动端

ARC 优化

《iOS和macOS性能优化:Cocoa、Cocoa Touch、Objective-C和Swift》第7章内存:陷阱和优化技巧,本章将围绕这个话题来进行讨论。除此之外,我们将展示一些陷阱,尤其是在Objective-C 代码中经常出现的问题。本节为大家介绍ARC 优化。

作者:李俊阳 等译来源:电子工业出版社|2018-07-17 18:01

ARC 优化

我们在处理Wunderlist 3.0 发布版时,突然在NSOutlineView 委托方法中遇到了一个崩溃,如示例7.11 所示。

示例7.11 返回0 的方法

  1. - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item  
  2. {  
  3. return NO;  

这里出现崩溃着实令人惊讶,因为看起来并没有导致崩溃发生的理由。我们期望汇编代码能够像示例7.12 那样,仅仅只操纵寄存器。事实上,如果只存在xorl %eax,%eax,retq 这条语句,那么我会很高兴,不过配置栈帧并不会带来太多的额外开销,这只是一个调试版本而已。

示例7.12 在非ARC 环境下,该方法所生成的汇编代码

  1. -[WLTaskListsDataSource outlineView:isGroupItem:]:  
  2. 01000e958a pushq %rbp  
  3. 01000e958b movq %rsp, %rbp  
  4. 01000e958e xorl %eax, %eax  
  5. 01000e9590 popq %rbp  
  6. 01000e9591 retq 

然而, 我们实际上获得的是示例7.13 中的代码。所有参数都会通过调用objc_storeStrong()而间接保留,然后通过再次调用objc_storeStrong()来释放,但是这只是清除了局部变量。这个崩溃似乎是由于NSOutlineView 传递了一个无效对象所引起的,这令我们感到万分惊愕,因为考虑到(a)这是ARC,按理来说这种事情是不可能发生的,此外(b)这是Apple 自己的代码。

示例7.13 在ARC 环境下,该方法所生成的汇编代码

  1. -[SomeOutlineViewDelegeate outlineView:isGroupItem:]:  
  2. 01001bfdb0 pushq %rbp  
  3. 01001bfdb1 movq %rsp, %rbp  
  4. 01001bfdb4 subq $0x30, %rsp  
  5. 01001bfdb8 leaq -0x18(%rbp), %rax  
  6. 01001bfdbc movq %rdi, -0x8(%rbp)  
  7. 01001bfdc0 movq %rsi, -0x10(%rbp)  
  8. 01001bfdc4 movq $0x0, -0x18(%rbp)  
  9. 01001bfdcc movq %rax, %rdi  
  10. 01001bfdcf movq %rdx, %rsi  
  11. 01001bfdd2 movq %rcx, -0x30(%rbp)  
  12. 01001bfdd6 callq 0x10027dbaa ## _objc_storeStrong  
  13. 01001bfddb leaq -0x20(%rbp), %rdi  
  14. 01001bfddf movq $0x0, -0x20(%rbp)  
  15. 01001bfde7 movq -0x30(%rbp), %rsi  
  16. 01001bfdeb callq 0x10027dbaa ## _objc_storeStrong  
  17. 01001bfdf0 leaq -0x20(%rbp), %rdi  
  18. 01001bfdf4 movabsq $0x0, %rsi  
  19. 01001bfdfe movl $0x1, -0x24(%rbp)  
  20. 01001bfe05 callq 0x10027dbaa ## _objc_storeStrong  
  21. 01001bfe0a movabsq $0x0, %rsi  
  22. 01001bfe14 leaq -0x18(%rbp), %rax  
  23. 01001bfe18 movq %rax, %rdi  
  24. 01001bfe1b callq 0x10027dbaa ## _objc_storeStrong  
  25. 01001bfe20 movb $0x0, %r8b  
  26. 01001bfe23 movsbl %r8b, %eax  
  27. 01001bfe27 addq $0x30, %rsp  
  28. 01001bfe2b popq %rbp  
  29. 01001bfe2c retq 

尽管最终还是找到并移除了崩溃点,但是测量结果也表明,ARC 代码大约是非ARC代码的3.8%。这不仅仅是优化过和未优化过之间的速度差异了,因为差异高出了好几个数量级。

为什么我们要运行未优化的代码呢?因为对于调试版本来说,Xcode 附带的默认构建设置(如图7.8 所示)被设定为关闭优化功能,这意味着几乎所有的开发构建版本运行的都是未优化的代码。

我们并未注意到这变为原来的3.8%的速度迟缓,因为这个方法位于占比97%的非性能关键代码中,并且经验阐明,性能关键代码和非关键代码之间具有极强的区分性。

尽管在这种情况下,优化器可以挺身而出(只是留下一个潜在的问题:调试版本的编译速度将相当缓慢),但这并不总能奏效。进一步的检查显示,即便使用了优化,绝大多数非常简单的方法也可能会产生很多不明显的objc_storeStrong()调用,这使得我在方法中所做的努力付诸东流。要处理这种情况有两种方法,一是将性能关键代码移动到非ARC 编译的文件中;二是在必要的时候添加__unsafe_unretained 属性,直到额外的retain 操作消失为止。如示例7.14 所示,这样就不会产生任何的外部保留操作,即便已经关闭了优化选项。

示例7.14 这个返回0 的方法的参数不会对其参数进行retain

  1. - (BOOL)outlineView:(__unsafe_unretained NSOutlineView *)outlineView  
  2. isGroupItem:(__unsafe_unretained id)item  
  3. {  
  4. return NO;  

我个人更偏好将性能关键代码从ARC 中完全剥离出去,转而依赖ARC 与非ARC的交互性。例如,在第3 章的“对象创建与缓存”一节中,对象缓存就无法用ARC 来表示,它必须位于一个单独的非ARC 文件中。此外,ARC 模式宣称“我知道我在做什么”让我很不安,因为我其实并不能确定我的所作所为是否和我的所思所想相同。在很多情况下,与编译器的交互结果着实让人惊愕。

喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书频道活动讨论群:365934973

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

数据库系统概念

本书是数据库系统方面的经典教材之一。国际上许多著名大学包括斯坦福大学、耶鲁大学、得克萨斯大学、康奈尔大学、伊利诺伊大学、印度理工学...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊