Dispatch를 이용한 성능 최적화
이는 Swift의 Dispatch 매커니즘과 관련이 있다. 보통 Dispatch라고 하면 Thread와 관련 있는 DispatchQueue를 떠올리게 되는데 이와는 다소 다른, 어떤 메서드를 호출할 것인지 결정하고 실행하는 매커니즘을 의미한다(Method Dispatch). Swift에서는 Static Dispatch와 Dynamic Dispatch, 두 가지의 방식이 존재한다.
Static Dispatch
호출될 함수를 '컴파일 타임'에 결정하고 런타임 때 실행하는 매커니즘으로서 함수의 호출이 결정나므로 성능이 좋다.
Dynamic Dispatch
호출될 함수를 '런타임'에 결정한다. 이러한 매커니즘을 위해 Swift에서는 클래스마다 vTable(Virtual Dispatch Table)을 유지한다. 함수 포인터들의 배열로 표현되는 vTable은 하위 클래스가 상위의 메서드를 호출할 때, 해당 배열을 참조하여 실제 호출할 함수를 결정하는데 이러한 과정들이 런타임에 결정되어 성능이 떨어지는 편이다.
위와 같은 특성들로 인해 참조 타입은 Dynamic Dispatch를(상속, 즉 오버라이딩의 가능성이 있으므로), 값 타입은 Static Dispatch를 사용한다. 위와 같은 내용을 확인하자면 Class는 Dynamic Dispatch를 사용하여 성능이 떨어진다는 것인데 그렇다면 성능을 향상시키기 위해서는 Static Dispatch를 사용하게끔 만들어주면 된다는 걸 유추할 수 있다.
final이 붙은 클래스는 상속이 불가, 메서드나 프로퍼티는 final을 선언하면 하위 클래스에서 이들을 오버라이딩할 수 없게 되므로 자연스럽게 Static Dispatch로 동작하게 된다.
private으로 선언할 경우, 참조 가능한 범위가 현재의 파일로 제한되며 이에 컴파일러는 해당 프로퍼티가 참조될 수 있는 구역 내에서 오버라이딩이 될 지, 안 될 지를 알아서 판단할 수 있게 된다. 만약 오버라이딩되는 곳이 없다는 게 판단될 경우에는 컴파일러 스스로가 final로 추론해서 Static Dispatch로 동작시키게 된다.
모듈 전체를 하나의 덩어리로 컴파일하기 때문에 internal level에 대해서 오버라이딩이 되는지, 아닌지를 추론 가능하고 오버라이딩이 없을 경우 final을 선언해주는 것이 Whole Module이며 Xcode의 Compilation Mode의 Release에서 설정 가능하다(현재는 자동으로 Whole Module이 켜져 있음).
컴파일을 파일 단위로 하게 되면 현재 파일의 Class가 상속이 없음에도 불구하고 다른 파일에서는 어떻게 활용될 지 알 수가 없으므로 자동으로 final로 동작시켜줄 수가 없다. 이러한 단점을 극복한 것이 WMO라는 것이다. 다만 internal보다 상위의 접근제어자(public, open) 키워드가 선언된 경우에는 외부 모듈에서도 접근이 가능하므로 WMO를 사용해도 Dynamic Dispatch로 작동한다.
final / private 키워드 활용, WMO 사용
단순히 final을 쓰면 Class의 성능이 좋아진다는 한 마디 덕분에 Method Dispatch에 대한 찍먹과 이를 활용한 Class 성능 향상 방법을 알아볼 수 있게 되었다. 오늘의 공부를 통해 앞으로는 Static Dispatch를 작동시킬 수 있도록 Class를 구현하여 성능을 최적화할 수 있도록 노력할 수 있게 될 것 같다.