Improving Swift Code Performance

由于Swift是一门静态的、强类型的语言,因此Swift编译器能够在编译阶段就对代码做大量优化,本文将探讨Swift编译器会对代码做哪些优化,我们该如何利用编译器的优化特性写出更高效的Swift代码。

Premature optimization is the root of all evil.
—–by Donald Knut

显式和隐式性能优化

只有当你发现了性能问题,并找到了原因之后,才应该开始做性能优化。

性能优化有两种类型:

  • 显式的
  • 隐式的

显式的性能优化直接指向低效的代码,这类优化通常要求对代码做重大的修改,比如换一种更加高效的算法。这通常会降低代码的可读性。使用更多的内存做缓存也能提高性能。

隐式的性能优化则需要借助语言特性来提高性能。它通常不需要对代码做重大修改,对代码可读性也没有负面影响,有时甚至能提高可读性。它被称为隐式是因为你能在代码的任何地方使用它,但过一段时间之后你又不会感觉到它的存在。

这里主要将探讨如何利用Swift的语言特性来做隐式的性能优化。

使用常量代替变量

相比于变量,使用常量拥有更高的性能。因为常量能清晰地告诉编译器,这个变量的值将不会被改变,编译器就会对它做内联优化,并不会为它分配内存空间,这样既能节省内存空间,也能提高运行速度。

方法调用

两种主要的方法调用类型:

  • 静态: 静态方法绑定是指当你调用一个方法时,编译器知道你确实是在调用这个类的这个方法。
  • 动态: 动态方法绑定在方法和对象之间是一种弱绑定。当你调用一个对象的某个方法时,无法保证这个对象一定就能执行这个方法。

Objective-C就是一种动态类型语言,并且拥有动态运行时。它采用消息发送机制来调用方法。当你调用一个方法时,实际上是发送了一个消息给这个对象,这个对象接受到这个消息后,必须先检查一下它是否实现了这个方法。

而Swift使用的是静态方法绑定,它拥有一个虚方法表,记录了一个类型拥有的所有函数指针。当在Swift中调用一个方法时,能够直接拿到这个方法的内存地址,然后执行它。这样就不需要去检查是否实现了这个方法。

全局函数(Global function)和静态方法(Static method)的执行速度是最快的。

类方法(Class method)分为两种情况,一种是不能被重写时,它用于和全局函数和静态方法一样的执行效率,一种是能够被重写时,当方法调用者在编译时是不确定的,那编译器就不能对它做优化,它的执行效率就会稍差。比如下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SuperClass {
class func test() {
print("super class test")
}
}

class SubClass: SuperClass {
override class func test() {
print("sub class test")
}
}

let instance = arc4random() % 2 == 0 ? SuperClass.self : SubClass.self
instance.test()

如果给类方法或者给类加上final修饰词,表明这个类或方法不能被继承重写,那这个类方法也将拥有和全局函数、静态方法一样的性能。

实例方法和类方法相似,因为有继承重写的因素,编译器能否对实例方法进行优化,取决于编译器能否确切地知道实例方法的具体调用者。

Intelligent code

因为Swift是一门静态的、强类型的语言,因此它会尝试去移除所有不必要的代码执行,比如那些没有被使用过的代码,或者对应用没有任何实际影响的代码。

1
2
3
4
5
6
7

class Test {
func doNothing() { }
}

Test().doNothing()
Test().doNothing()

在这段代码中,doNothing方法没有做任何事,Swift编译器就会自动移除Test().doNothing()这两句代码。

到OC就没有这种优化,因为OC可以在运行时可以改变一个方法的实际调用和实现。

但是Swift编译器不能自动忽略print方法,所以我们在程序里通过print打log时,可以采用只有在debug模式下执行print方法的方式,这样在release模式下,编译器就能忽略print方法了。

Using nonoptimizable constants

在swift中,有些类型在类中使用时,不能和其它类型一样拥有上面的优化特性,即使是使用了常量。这些类型有:

  • String
  • Array
  • Custom Class objects
  • Closures
  • Set
  • Dictionary

以上几种类型因为要暴露给OC使用,内部会维持一个OC的相应类型,比如Sting对应NSString。这样就拥有了一些OC的运行时特性,因此编译器无法自动忽略它们。

但如果是在struct中使用这些类型,那这些类型就不会暴露给OC使用,编译器就又能为它们做优化了。

一些提高代码速度的方法

  1. 使用final
  2. 使用@inline(__always)是函数始终编译成内联函数
  3. 使用不可变的值类型
  4. 避免在Swift代码中使用OC类型
  5. 避免暴露Swift代码给OC使用
  6. 避免使用OC的动态特性
坚持原创技术分享,您的支持将鼓励我继续创作!