在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
生活中,我们总是忙忙碌碌,追寻着各自的梦想。到头来,你会发现很多外在的装饰都不重要,重要的是那颗看待世界万物的心,和依旧充满活波的灵魂。脸上的粉底不重要,重要的是你的气质。口中的言语不重要,重要的是你的涵养。心中的学识不重要,重要的是你是否有爱。 在這裡提供有愛的你一些技巧可以帮助提高你的 Swift 程序质量,并且可以减少代码中的容易出现的错误,使代码更具可读性。显式地标记出最终类和类的协议是两个显而易见的例子。然而,文章中描述的一些技巧是不符合规定的,扭曲的,仅仅解决由于编译器或者语言暂时限制的问题。文章中的建议来自多方面的权衡,例如程序运行时,二进制大小,代码可读性等等。 2010年的夏天,Chris Lattner 接到了一个不同寻常的任务:为 OS X 和 iOS 平台开发下一代新的编程语言。那时候乔布斯还在以带病之身掌控着庞大的苹果帝国,他是否参与了这个研发计划,我们不得而知,不过我想他至少应该知道此事,因为这个计划是高度机密的,只有极少数人知道,最初的执行者也只有一个人,那就是 Chris Lattner。 从2010年的7月起,克里斯(Chris)就开始了无休止的思考、设计、编程和调试,他用了近一年的时间实现了大部分基础语言结构,之后另一些语言专家加入进来持续改进。到了2013年,该项目成为了苹果开发工具组的重中之重,克里斯带领着他的团队逐步完成了一门全新语言的语法设计、编译器、运行时、框架、IDE 和文档等相关工作,并在2014年的 WWDC 大会上首次登台亮相便震惊了世界,这门语言的名字叫做:「Swift」。 ———————— 每个人应该做的第一件事是启用优化。 Swift 提供了三种不同的优化级别: -Onone: 这是为正常开发准备,它执行最少的优化并保留所有调试的信息。 -O: 是为大多数生产代码准备,编译器执行积极的优化,可以很大程度上改变提交代码的类型和数量。调试信息同样会被输出,但是有损耗。 -Ounchecked: 这个是特定的优化模式,为了特定的库或应用程序,舍弃安全性来提高性能。编译器将移除所有溢出检查以及一些隐式类型检查。由于这样会导致未被发现的存储安全问题和整数溢出,所以一般情况下并不会使用这种模式。仅使用于你已经仔细审查了自己的代码对于整数溢出和类型转换是友好的情况下。 在 Xcode UI 中,人们可以按照下面修改当前优化级别: 优化整个组件默认情况下 Swift 单独编译每个文件。这使得 Xcode 可以非常快速的并行编译多个文件。然而,分开编译每个文件可以阻止某些编译器优化。 Swift 也可以把整个程序看做一个文件来编译,并把程序当成单个编译单元来优化。这个模式可以使用命令行 -whole-module-optimization 来启用。在这种模式下程序需要花费更长的时间来编译,但运行起来却更快。 这个模式可以通过 Xcode 构建设置中的'Whole Module Optimization'来启用。 降低动态调度 (Reducing Dynamic Dispatch)在默认情况下, Swift 是一个类似 Objective-C 的动态语言。与 Objective-C 不同的是,程序员在必要的时候可移除或减少 Swift 这种动态特性,从而提高运行时性能。本节将提供几个能够被用于操作语言结构的例子。 在默认情况下,类使用动态调度的方法和属性访问。因此在下面的代码片段中, a.aProperty, a.doSomething() 和 a.doSomethingElse() 都将通过动态调度来调用: class A { var aProperty: [Int] func doSomething() { ... } dynamic doSomethingElse() { ... } } class B : A { override var aProperty { get { ... } set { ... } } override func doSomething() { ... } } func usingAnA(a: A) { a.doSomething() a.aProperty = ... } 在 Swift 中,动态调度默认通过一个 vtable [1] (虚函数表)间接调用。如果使用 dynamic 关键字声明, Swift 的调用方式将变为:『通过 Objective-C 的消息传递机制 』。在上面这两种情况下,后者『通过 Objective-C 的消息传递』要比直接进行函数调用慢,因为他阻止了编译器的很多优化 [2] ,除了自身的间接调用开销。在性能优先的代码中,人们常常想限制这种动态行为。 建议:当你在声明时知道不会被重写时使用 'final' final 关键字是类、方法或属性声明中的限制,从而让声明不被重写。这就意味着编译器可以使用直接函数调用代替间接函数调用。例如下面的 C.array1 和 D.array1 将会被直接访问 。与之相反, D.array2 将通过一个虚函数表访问: final class C { // 类'C'中没有声明可以被重写 var array1: [Int] func doSomething() { ... } } class D { final var array1 [Int] //'array1'不可以被计算属性重写 var array2: [Int] //'array2'*可以*被计算属性重写 } func usingC(c: C) { c.array1[i] = ... //可以直接使用C.array而不用通过动态调用 c.doSomething() = ... //可以直接调用C.doSomething而不用通过虚函数表访问 } func usingD(d: D) { d.array1[i] = ... //可以直接使用D.array1而不用通过动态调用 d.array2[i] = ... //将通过动态调用使用D.array2 } 高效地使用容器类型通用的容器 Array 和 Dictionary 是 Swift 标准库提供的一个重要特性。本节将解释如何用高性能方式使用这些类型。 建议:在数组中使用值类型 在 Swift 中,类型可以分为不同的两类:值类型(结构体,枚举,元组)和引用类型(类)。一个关键的差别就是 NSArray 中不能含有值类型。因此当使用值类型时,优化器就不需要去处理对 NSArray 的支持,从而可以在数组上省去大部分的消耗。 此外,相比引用类型,如果值类型递归地包含引用类型,那么值类型仅需要引用计数器。使用不含引用类型的值类型,就可以避免额外的开销(数组内的元素执行 retain、release 操作所产生的通讯量)。 // 这里不要使用类 struct PhonebookEntry { var name : String var number : [Int] } var a : [PhonebookEntry] 牢记在使用大的值类型和引用类型之间要做好权衡。在某些情况下,拷贝和移动大的值类型消耗要大于移除桥接和保留/释放的消耗。 ———————— 在 Swift 中,值保留有一份独有的数据拷贝。使用值类型有很多优点,比如能保证值具有独立的状态。当我们拷贝值时(等同于分配,初始化和参数传递),程序将会创建一份新的拷贝。对于一些大的值类型,这样的拷贝是相当耗时的,也可能会影响到程序的性能。 考虑下面的代码,代码中使用'值'类型的节点定义了一棵树。树的节点包括其它使用协议的节点。计算机图形场景通常由不同的实体和变形体构成,而他们都能表示为值的形式,所以这个例子很有实际意义。 protocol P {} struct Node : P { var left, right : P? } struct Tree { var node : P? init() { ... } } 当数进行拷贝(传递参数,初始化或者赋值操作),整棵树都要被拷贝。这是一个花销很大的操作,需要调用很多 mallocfree (分配释放)以及大量引用计数操作。 然而,我们并不是真的关心值是否被拷贝,只要这些值还保留在内存中。 ———————— 不安全的代码> Swift 中类总是采用引用计数。 Swift 编译器会在每次对象被访问时插入增加引用计数的代码。例如,考虑一个通过使用类实现遍历链表的例子。遍历链表是通过从一个节点到下一个节点移动引用实现: elem = elem.next。每次我们移动这个引用, Swift 将会增加 next 对象的引用计数,并且减少前一个对象的引用计数。这样的引用计数方法成本很高,但只要我们使用 Swift 的类就无法避免。 final class Node { var next: Node? var data: Int ... } 建议:使用非托管的引用来避免引用计数带来的开销 在性能优先代码中,你可以选择使用未托管的引用。其中 Unmanaged 结构体就允许开发者关闭对于特殊引用的自动引用计数 (ARC) 功能。 var Ref : Unmanaged = Unmanaged.passUnretained(Head) while let Next = Ref.takeUnretainedValue().next { ... Ref = Unmanaged.passUnretained(Next) } 建议:标记只能由类实现的协议为类协议 > Swift 可以限定协议只能通过类实现。标记协议只能由类实现的一个优点就是,编译器可以基于只有类实现协议这一事实来优化程序。例如,如果 ARC 内存管理系统知道正在处理类对象,那么就能够简单的保留(增加对象的引用计数)它。如果编译器不知道这一事实,它就不得不假设结构体也可以实现协议,那么就需要准备保留或者释放不可忽视的结构体,这样做的代价很高。 ———————— 经过 WWDC2014,苹果已经完全体现出了一个软件公司的创新能力和技术底蕴,无论是操作系统,编程语言,还是应用开发,苹果都已经准备好了,凝神静气,蓄势待发。作为开发者,我们要做的就是:Write the code, Change the world,然后期待下一个收获的季节! 曾经我们都饱受非人类的Objective-C折磨,如今,我们需要拥抱全新的生活方式:一种生活美学,一种生活态度,一种生活哲学。这种生活态度和哲学,是经历过风霜雪雨和世事沧桑,才得以沉淀。很多背后故事的故事,也许只有岁月能懂。 但是現在不用这么麻烦了!7 月 14 日巧神跟大家分享 Swift性能优化分析 |
请发表评论