swift는 수많은 좋은 기능을 가진 유연하고 안전한 언어다.
근데 이런 많은 기능 때문에 느려지는 게 아니냐고 생각할 수도 있다.
하지만 swift는 고도로 최적화된 네이티브 코드를 내보냄으로써 오버헤드를 최소화 한다.
모든 최적화를 볼 수 없으니 하나만 보자. Array Bounds Checks Optimizations
swift는 배열에서 인덱스 접근이 일어날 때, 범위내에서 일어나는 것을 검사한다.
// 작성하는 코드
for i in 0..<n {
A[i] ^= 13
}
// 인덱스 접근이 일어날 때 실제로는 검사한다.
for i in 0..<n {
precondition(i<length)
A[i] ^= 13
}
다만 이러한 검사는 속도를 느리게 하고, 다른 최적화(vectorization 등)를 못하게 한다.
그래서 루프마다 검사하는 게 아니라, 루프시작할 때 1번만 검사하도록 한다.
precondition(n<=length)
for i in 0..<n {
A[i] ^= 13
}
이러한 최적화는 수많은 프로그램과 벤치마크를 추적하면서 유효성을 입증한 전략이다.
최적화 된 코드 뿐 아니라 최적화되지 않은 코드도 빠르게 해야 한다.
이를 위해서 두가지를 했다.
WMO(Whole Module Optimization)
컴파일러는 일반적으로는 대부분의 참조 카운팅에 의한 오버헤드를 없앨 수 있다.
하지만 가끔은 여전히 참조 카운팅에 의한 느려짐을 겪는 경우가 있다.
참조 카운팅의 동작
class C { ... }
func foo(c: C?) P { ... }
var x: C? = C() // 최초 할당, count = 1
var y: C? = x // 다른 변수에 할당, count = 2
foo(y) // 임시 변수를 만들어서 y의 값을 할당, count = 3
// foo 실행 종료 후 임시 변수 사라짐. count = 2
y = nil // count = 1
x = nil // count = 0
struct는 그 자체적으로는 참조 카운팅이 없지만, 클래스 프로퍼티를 가지면 프로퍼티가 참조 카운팅을 하기 때문에 실질적으로는 참조 카운팅을 하게 된다.
코드로는 간단하다.
func min<T: Comparable>(x: T, y: T) -> T {
return y < x ? y : x
}
실제 컴파일러가 내보내는 코드는 좀 더 복잡하다.
indirection을 사용하고 있다. 실제로 어떤 타입이 넘어갈 지 모르니까.
T가 참조 카운팅이 필요한 타입인지조차 알 수 없기 때문에, 참조 카운팅을 위한 코드도 indirection해서 넣어야 한다.
// psuedo-Swift
func min<T: Comparable>(x: T, y: T, FTable: FunctionTable) -> T {
let xCopy = FTable.copy(x)
let yCopy = FTable.copy(y)
let m = FTable.lessThan(yCopy, xCopy) ? y : x
FTable.release(x)
FTable.release(y)
return m
}
이 오버헤드를 없애기 위해서 컴파일러는 generic specialization이라는 최적화 기법을 사용한다.
해당 함수가 호출되는 부분을 찾아서, 구체 타입을 확인 후 해당 구체 타입을 타입 매개변수에 직접 넣어서 불필요한 코드를 없앤다.
이후 호출부를 이 특수화된 함수로 바꾼다.
func min<Int>(x: Int, y: Int) -> Int {
return y < x ? y : x
}
이 최적화는 강력하지만, visibility에 따라서 최적화가 돌아가지 못하는 경우가 많다.
대신 WMO가 켜져 있으면 가능하다.
클래스 상속 관계에서의 메소드 호출
public class Pet {
func noise() { ... }
var name: String { ... }
func noiseImpl()
}
class Dog: Pet {
override func noise() { ... }
}
func makeNoise(p: Pet) {
print("My name is \\(p.name)")
p.noise()
}
makeNoise 함수에 대해서 컴파일러가 내보내는 코드는 좀 더 복잡하다.
// psuedo-Swift
func makeNoise(p: Pet) {
let nameGetter = Pet.nameGetter(p)
print("My name is \\(nameGetter(p))")
let noiseMethod = Pet.noiseMethod(p)
noiseMethod(p)
}