Class
를 쓰게 되면 final
키워드를 붙여 상속이 안되게 해주자라는 의미로 작성했었는데
왜 그렇게 해야 좋은걸까 고민을 하기 시작했다.
Increasing Performance by Reducing Dynamic Dispatch를 읽고 정리했습니다.
다른 언어와 마찬가지로 Swift도 SuperClass
에 선언된 메소드와 프로퍼티 선언을 override
할 수 있다.
프로그램이 런타임에 어떤 메소드와 프로퍼티를 참조할지 결정하고,
indirect call
(간접 호출), indirect access
(간접 접근)을 수행해야 한다.
이것을 Dynamic Dispatch
이라 불리며,
indirect call
(간접 호출) / indirect access
(간접 접근)을 사용할때마다 런타임 오버헤드
비용을 증가시킨다.
성능에 민감한 코드에서는 오버헤드는 바람직하지 못하다
다이나믹함을 제거함으로써 성능을 향상시키는 3가지 방법
final
private
모듈 최적화
하나씩 예제를 가지고 살펴보자
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)
}
위 코드를 보면 컴파일러는 동적으로 호출한다.
ParticleModel의 서브 클래스들이 메소드와 프로퍼티를 오버라이딩
을 할 수 있기 때문에 동적으로 호출된다.
Swift에서 동적인 호출
은 메서드 테이블에서 찾고 간접 호출을 수행한다.
final
은 클래스의 메소드, 프로퍼티가 오버라이드
할 수 없도록 하는 키워드이다.
컴파일러가 동적인 간접 호출
, 간접 접근
을 생략할 수 있게한다.
// 프로퍼티와 메소드에 final
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
final class ParticleModel {
var point = ( x: 0.0, y: 0.0 )
var velocity = 100.0
// ...
}
private
키워드를 선언부에 작성하면 현재 파일에만 선언이 노출되도록 제한한다.오버라이딩 선언
을 찾을 수 있도록 한다.final
키워드를 추론하고 메소드와 프로퍼티 접근에 대한 간접 호출
, 간접 접근
을 제거한다.// 프로퍼티와 메소드에 private
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)
}
}
// class에 private
private class ParticleModel {
var point = ( x: 0.0, y: 0.0 )
var velocity = 100.0
// ...
}
internal
접근자(default)는 선언된 모듈 내에서만 볼 수 있다.
Swift는 일반적으로 모듈을 구성하는 파일들을 개별적으로 컴파일하기 때문에
컴파일러는 internal
선언이 다른 파일에서 오버라이딩 되었는지 알 수 없다.
전체 모듈 최적화
옵션을 활성화하면 모든 모듈이 동시에 컴파일된다.
전체 모듈에 대해 컴파일러가 추론이 가능하게 되어 오버라이딩 되지 않은 internal을 final로 추론
할 수 있게된다.
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로 추론이 안된다.