和其他许多语言一样,Swift 允许一个类的子类重写方法和属性。这就意味着程序必须在运行时决定关联哪个方法或属性,从而间接调用或间接访问。这项叫动态派送的技术,以每次间接调用固定的运行时开销为代价,增强了语言的表达性。如果对于性能敏感,这种方法往往是不可取的。这篇文章将介绍3种消除动态机制来提升性能的方法:final
, private
和全局模块优化。
先细读下面例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ParticleModel { var point = ( 0.0, 0.0 ) var velocity = 100.0 func updatePoint(newPoint: (Double, Double), newVelocity: Double) { point = newPoint velocity = newVelocity } func update(newP: (Double, Double), newV: Double) { updatePoint(newP, newVelocity: newV) } } var p = ParticleModel() for i in stride(from: 0.0, through: 360, by: 1.0) { p.update((i * sin(i), i), newV:i*1000) } |
如上所写,编译器会建立一个动态派送调用:
- 调用
update()
, - 调用
updatePoint()
, - Get 属性
point
元组, - Get 属性
velocity
。
当你看这段代码时,这可能和你预想的不一样。因为 ParticleModel
的子类可能使用计算属性重写 point
或 velocity
,或者重写 updatePoint()
或 update()
方法,因此动态调用是必要的。
在 Swift,执行动态派送调用就是在方法列表里寻找函数,然后间接调用。这会慢于直接调用。另外,间接调用阻止了许多编译器优化,使得使用间接调用成本更高。在性能要求严格的情况下,这里有一些技术可以在不需要动态行为的时候提升性能。
当你确定声明不需要被重写时,使用 final
关键字 final
可以标示类、方法和属性的声明(declaration)不能被重写。编译器便会安全地省略动态派送。例如在下面这个代码,访问 point
和 velocity
会直接加载对象的储存属性,并且调用 updatePoint()
是直接调用。另一方面,update()
还是会通过动态派送调用,也允许子类重写 update()
。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class ParticleModel { final var point = ( x: 0.0, y: 0.0 ) final var velocity = 100.0 final func updatePoint(newPoint: (Double, Double), newVelocity: Double) { point = newPoint velocity = newVelocity } func update(newP: (Double, Double), newV: Double) { updatePoint(newP, newVelocity: newV) } } |
把整个类标记 final
也是可行的,只需加在 class 前面。这会禁止子类继承,隐式地声明该类所有的函数和属性都是 final
。
1 2 3 4 5 |
final class ParticleModel { var point = ( x: 0.0, y: 0.0 ) var velocity = 100.0 // ... } |
通过应用 private 关键字,把仅在一个文件中出现的声明推断为 final
给声明(declaration)应用 private
关键字会把它限制为仅在当前文件可见。这允许编译器去寻找所有潜在可重写的声明。未存在重写的声明会使得编译器自动推断为 final
,去除访问方法和属性时的间接调用。
假设没有一个类在当前文件重写了 ParticleModel
,编译器会将所有 private 声明的动态派送调用替换为直接调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class ParticleModel { private var point = ( x: 0.0, y: 0.0 ) private var velocity = 100.0 private func updatePoint(newPoint: (Double, Double), newVelocity: Double) { point = newPoint velocity = newVelocity } func update(newP: (Double, Double), newV: Double) { updatePoint(newP, newVelocity: newV) } } |
上面这个例子,point
和 velocity
会被直接访问,updatePoint()
会被直接调用。而 update()
则再一次被间接调用,因没有 private 标记。
和 final
一样,给类声明标记 private
也是可以的,这回使得类是私有的,它的所有属性和方法也是私有的。
1 2 3 4 5 |
private class ParticleModel { var point = ( x: 0.0, y: 0.0 ) var velocity = 100.0 // ... } |
使用全局模块优化,将 internal 声明推断为 final
带有 internal
访问的声明(如果没有显式声明,默认也是如此)只有在当前模块才可见。因为 Swift 通常会分开编译同一模块的不同文件,编译器不能推断一个 internal
声明会不会在其他文件中被重写。但是,如果全局模块优化可用,所有的模块会在同一时间被编译。这就允许编译器将所有模块一起处理,如果 internal
声明没有可见的重写,就推断为 final。
回到最开始的代码,这次我们给 ParticleModel
加些 public
关键字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class ParticleModel { var point = ( x: 0.0, y: 0.0 ) var velocity = 100.0 func updatePoint(newPoint: (Double, Double), newVelocity: Double) { point = newPoint velocity = newVelocity } public func update(newP: (Double, Double), newV: Double) { updatePoint(newP, newVelocity: newV) } } var p = ParticleModel() for i in stride(from: 0.0, through: times, by: 1.0) { p.update((i * sin(i), i), newV:i*1000) } |
当用全局模块优化编译这段代码,编译器会推断属性 point,velocity
和方法 updatePoint()
为 final
。相对的,update()
被标记为 public,不会被推断为 final
。