在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
Swift 性能探索和优化分析本文首发在 CSDN《程序员》杂志,订阅地址 http://dingyue.programmer.com.cn/。 Apple 在推出 Swift 时就将其冠以先进,安全和高效的新一代编程语言之名。前两点在 Swift 的语法和语言特性中已经表现得淋漓尽致:像是尾随闭包,枚举关联值,可选值和强制的类型安全等都是 Swift 显而易见的优点。但是对于高效一点,就没有那么明显了。在 2014 年 WWDC 大会上 Apple 宣称 Swift 具有超越 Objective-C 的性能,甚至某些情况下可以媲美和超过 C。但是在 Swift 正式发布后,很多开发者发现似乎 Swift 性能并没有像宣传的那样优秀。甚至在 Swift 经过了一年半的演进的今天,稍有不慎就容易掉进语言性能的陷阱中。本文将分析一些使用 Swift 进行 iOS/OS X 开发时性能上的考量和做法,同时,笔者结合自己这一年多来使用 Swift 进行开发的经验,也给出了一些对应办法。 为什么 Swift 的性能值得期待Swift 具有一门高效语言所需要具备的绝大部分特点。与 Ruby 或者 Python 这样的解释型语言不需要再做什么对比了,相较于其前辈的 Objective-C,Swift 在编译期间就完成了方法的绑定,因此方法调用上不再是类似于 Smalltalk 的消息发送,而是直接获取方法地址并进行调用。虽然 Objective-C 对运行时查找方法的过程进行了缓存和大量的优化,但是不可否认 Swift 的调用方式会更加迅速和高效。 另外,与 Objective-C 不同,Swift 是一门强类型的语言,这意味 Swift 的运行时和代码编译期间的类型是一致的,这样编译器可以得到足够的信息来在生成中间码和机器码时进行优化。虽然都使用 LLVM 工具链进行编译,但是 Swift 的编译过程相比于 Objective-C 要多一个环节 -- 生成 Swift 中间代码 (Swift Intermediate Language,SIL)。SIL 中包含有很多根据类型假定的转换,这为之后进一步在更低层级优化提供了良好的基础,分析 SIL 也是我们探索 Swift 性能的有效方法。 最后,Swift 具有良好的内存使用的策略和结构。Swift 标准库中绝大部分类型都是 对性能进行测试《计算机程序设计艺术》和 TeX 的作者高德纳曾经在论文中说过:
和很多人理解的不同,这并不是说我们不应该在项目的早期就开始进行优化,而是指我们需要弄清代码中性能真正的问题和希望达到的目标后再开始进行优化。因此,我们需要知道性能问题到底出在哪儿。对程序性能的测试一定是优化的第一步。 在 Cocoa 开发中,对于性能的测试有几种常见的方式。其中最简单是直接通过输出 log 来监测某一段程序运行所消耗的时间。在 Cocoa 中我们可以使用
为了方便使用,我们还可以将这段代码封装到一个方法中,这样我们就能在项目中需要测试性能的地方方便地使用它了:
当我们寻找到需要进行优化的代码路径后,为其建立一个单元测试来持续地检测代码的性能是很好的做法。在 Xcode 中默认的测试框架 XCTest 提供了检测并汇报性能的方法:
优化手段,常见误用及对策多线程、算法及数据结构优化在确定了需要进行性能改善的代码后,一个最根本的优化方式是在程序设计层面进行改良。在移动客户端,对于影响了 UI 流畅度的代码,我们可以将其放到后台线程进行运行。Grand Central Dispatch (GCD) 或者
将工作放到其他线程虽然可以避免主线程阻塞,但它并不能减少这些代码实际的执行时间。进一步地,我们可以考虑改进算法和使用的数据结构来提高效率。根据实际项目中遇到的问题的不同,我们会有不同的解决方式,在这篇文章中,我们难以覆盖和深入去分析各种情况,所以这里我们只会提及一些共通的原则。 对于重复的工作,合理地利用缓存的方式可以极大提高效率,这是在优化时可以优先考虑的方式。Cocoa 开发中 在程序开发时,数据结构使用上的选择也是重要的一环。Swift 标准库提供了一些很基本的数据结构,比如 如果项目中有很多数学计算方面的工作导致了效率问题的话,考虑并行计算能极大改善程序性能。iOS 和 OS X 都有针对数学或者图形计算等数字信号处理方面进行了专门优化的框架:Accelerate.framework,利用相关的 API,我们可以轻松快速地完成很多经典的数字或者图像处理问题。因为这个框架只提供一组 C API,所以在 Swift 中直接使用会有一定困难。如果你的项目中要处理的计算相对简单的话,也可以使用 Surge,它是一个基于 Accelerate 框架的 Swift 项目,让我们能在代码里从并行计算中获得难以置信的性能提升。 编译器优化Swift 编译器十分智能,它能在编译期间帮助我们移除不需要的代码,或者将某些方法进行内联 (inline) 处理。编译器优化的强度可以在编译时通过参数进行控制,Xcode 工程默认情况下有 Debug 和 Release 两种编译配置,在 Debug 模式下,LLVM Code Generation 和 Swift Code Generation 都不开启优化,这能保证编译速度。而在 Release 模式下,LLVM 默认使用 "Fastest, Smallest [-Os]",Swift Compiler 默认使用 "Fast [-O]",作为优化级别。我们另外还有几个额外的优化级别可以选择,优化级别越高,编译器对于源码的改动幅度和开启的优化力度也就越大,同时编译期间消耗的时间也就越多。虽然绝大部分情况下没有问题,但是仍然需要当心的是,一些优化等级采用的是激进的优化策略,而禁用了一些检查。这可能在源码很复杂的情况下导致潜在的错误。如果你使用了很高的优化级别,请再三测试 Release 和 Debug 条件下程序运行的逻辑,以防止编译器优化所带来的问题。 值得一提的是,Swift 编译器有一个很有用的优化等级:"Fast, Whole Module Optimization",也即 虽然现在编译器在进行优化的时候已经足够智能了,但是在面对编写得非常复杂的情况时,很多本应实施的优化可能失效。因此保持代码的整洁、干净和简单,可以让编译器优化良好工作,以得到高效的机器码。 尽量使用 Swift 类型为了和 Objective-C 协同工作,很多 Swift 标准库类型和对应的 Cocoa 类型是可以隐式的类型转换的,比如
这是我们日常开发中很常见的代码,使用 尽可能避免混合地使用 Swift 类型和 避免无意义的 log,保持好的编码习惯在调试程序时,很多开发者喜欢用输出 log 的方式对代码的运行进行追踪,帮助理解。Swift 编译器并不会帮我们将 之后我们就可以通过将
这样,在 Release 版本中,
小结Swift 还是一门很新的语言,并且处于高速发展中。因为现在 Swift 只用于 Cocoa 开发,因此它和 Cocoa 框架还有着千丝万缕的联系。很多时候由于这些原因,我们对于 Swift 性能的评估并不公正。这门语言本身设计就是以高性能为考量的,而随着 Swift 的开源和进一步的进化,以及配套框架的全面重写,相信在语言层面上我们能获得更好的性能和编译器的支持。 最好的优化就是不用优化。在软件开发中,保证书写正确简洁的代码,在项目开始阶段就注意可能存在的性能缺陷,将可扩展性的考虑纳入软件构建中,按照实际需求进行优化,不要陷入为了优化而优化的怪圈,这些往往都可以让我们避免额外的优化时间,让我们的工作得更加愉快。 参考 |
请发表评论