공부의 발단..
KVO(key-value observing)에 대해서 공부를 하다가, dispatch에 관한 이야기가 나와서, 해당 부분에 대해서 난생처음 본 부분이라 공부하게 되었다.
컴파일과 런타임
가장 먼저 컴파일과 런타임에 대한 이해가 있어야 한다.
•
컴파일 : 컴파일이란, 우리가 작성한 코드가 어셈블리어, 즉 기계가 이해할수 있는 0과 1의 언어로 변환되는 과정을 의미한다.
•
런타임 : 런타임이란, 위에서 컴파일을 거친 코드가, 사용자에 의해 사용되어지는 순간을 의미한다.
Dispatch
Dispatch란 오버라이드 된 메서드가 존재할때,
어떤 메서드를 호출할 것인지를 결정하여, 그것을 실행하는 메커니즘이다
Swift에선 Static Dispatch와 Dynamic Dispatch 두 가지 방식이 있다
Static Dispatch
static dispatch는 “컴파일” 타임에 어떤 메소드를 호출할지를 정하는 방법이다.
컴파일 시점에서 정하기 때문에 성능상 이점을 가지게 된다.
주로 값 타입에서 사용하게 되는데, 그말은 struct혹은 enum에서 사용하게 된다.
그런데, 에초에 struct와 enum은 상속이 불가능 하니까 당연하게도 오버라이딩이 없다.
Dynamic Dispatch
dynamic dispatch는 “런타임”시점에서 어떤 메소드를 호출할지 정하는 방법이다.
dynamic dispatch의 경우, Swift에서는 클래스마다 함수 포인터들의 배열인 vTable(Virtual Methon Table)이라는 것을 유지하며, 하위 클래스에서 상속을 하고 해당 함수를 불렀다면 vTable을 참조하여 실제 호출할 함수를 정하게 된다.
이처럼 상속받았고, 해당 함수가 상속받은 클래스에 존재한다면, 해당 함수가 호출될때 그게 어디에 존재하는 함수인지를 확인하게 되는데 이러한 과정이 “런타임”에서 발생한다는것이다.
•
이를 통해서 성능이 저하된다. → 테이블을 읽고 → 해당 메소드로 이동해야 하는 과정을 거치기 때문.
•
이러한 과정에서 오버헤드가 발생함
또한 프로토콜에서도, 이와 같은 런타임 시점에서 메소드 호출을 확인하기 때문에 dynamic dispatch를 사용하게 된다.
즉 클래스와 프로토콜은 dynamic dispatch를 사용함.
이를 이용한 성능개선
위의 방법과 같이, class나 protocol에 의한 메소드들은 dynamic dispatch를 사용하게 되고, 이 때문에 오버헤드가 발생하여 앱 성능이 떨어지게 된다.
이를 방지하는 방법은 다음과 같다.
1.
final 키워드를 사용하기
기본적으로 앞서 말했듯이, dynamic dispatch를 사용하게 되는 이유는 우리가 상속을 통한 오버라이드로 인하여, 어떠한 메소드가 실행되는지 런타임에 체크를 해줘야 하는 문제를 해결하기 위한것이다.
따라서 final키워드를 사용하여 상속이 불가능하도록 하게 되면 자연스럽게 static dispatch를 사용하게 되며, 이는 앱 성능개선으로 이어진다.
2.
private 키워드를 사용하기
private 키워드를 붙여서 선언하게 되면 해당 요소는 한 파일 내에서만 참조하게 된다.
따라서 한 파일내에 해당 요소에 대한 오버라이드가 없는 경우 컴파일러가 이를 자동으로 static dispatch로 바꿔줄 수 있다.
3.
WMO(Whole Module Optimization)을 사용하기
WMO를 사용하게 되면 internal 선언만으로 final을 추론해낼 수 있습니다.(물론 모듈 내에 오버라이드가 없는 경우)
swift는 기본적으로 모듈을 이루는 파일들을 개별적으로 컴파일 하기 때문에 다른 파일에서 오버라이딩이 일어났는지 알 수 없다. 하지만 WMO를 활성화 하면 모듈 전체가 하나의 덩어리로 취급되어 컴파일 되기 때문에 모듈 내의 internal 선언은 모듈 바깥에 드러나지 않아 오버라이딩이 불가능 하다는 것이 보장되기 때문에 static dispatch를 사용하게 된다.
참고자료