성능 좋고 최적화가 잘된 Swift 코드는 어떤 코드일까요? 이에 대해 알아보았습니다.
그 전에 Xcode 설정부터 봅시다!
Whole Module Optimization
프로젝트 → 타겟 → 빌드 세팅 → Whole 검색

WMO(전체 모듈 최적화)는 Swift의 컴파일러 기술이고 WMO의 성능 향상은 프로젝트에 따라 최대 2배~5배까지도 될 수 있습니다.
위 이미지에서 Compilation Mode 부분을 보시면 Debug와 Release로 나눠져있죠.
Debug → 평소 개발 할 때의 상태. Release → 출시 할 때의 상태입니다.
보시다시피 기본적으로 Release할 때만 WMO가 활성화 되어있는 걸 확인 할 수 있습니다.
보통 Swift 컴파일러는 파일을 개별적으로 컴파일합니다. 이렇게하면 여러개의 파일을 병렬로 빠르게 컴파일할 수 있습니다.

소스 파일 읽기 → 구문 분석 → 최적화 → 코드 및 객체 파일 생성 → 모든 객체 파일 결합 → 실행 파일 생성 순으로 이뤄지죠.
하지만 이렇게 개별적으로 컴파일 할 땐 컴파일러의 최적화 범위는 단일 파일밖에 안됩니다. 이게 무슨 뜻이냐면요.
A파일에 제네릭 타입으로 반환하는 함수를 선언하고, B파일에 그 함수를 호출한다고 할 때,
컴파일러는 해당 제네릭에 단순 Int 타입이 들어와도 A파일에선 해당 함수가 구체적으로 어떤 타입인지 모르고, B파일에선 해당 함수가 어떻게 구현되어 있는지 모릅니다.
그래서 결국 어떤 메서드를 어떻게 실행시킬지 컴파일 시점에 결정하지 못하고 런타임 때 결정하는 Dynamic Dispatch가 이뤄지게 됩니다.
이렇게 각각의 파일을 컴파일하다보면 성능 최적화가 어려운 부분이 생기는데 WMO는 전체 코드를 하나의 파일처럼 컴파일하여 런타임 성능을 최적화할 수 있습니다. 구체적으로 두 가지 큰 장점이 있는데요.

1.컴파일러는 모든 함수 구현을 확인해서 미리 인라인 함수로 만들거나 함수 특수화 같은 최적화를 할 수 있습니다.
함수 특수화는 특정 호출 컨텍스트에 최적화된 새로운 버전의 함수를 생성하는 것을 의미합니다.
위 예시에서 A파일의 제네릭 함수를 미리 파악하여 Int타입을 반환한다는 새로운 함수로 만들어서 B파일에 인라인으로 연결시킬 수 있습니다.
2.컴파일러는 비공개 함수나 전혀 사용되지 않는 메서드가 있는지 파악할 수 있고, 만약 있다면 미리 제거할 수 있습니다.
물론 이렇게 전체 코드를 한번에 보다보니 컴파일 시간이 좀 늘어나는 단점이 있습니다.
Xcode가 하는 일 말고 개발자로서는 어떻게 코드를 작성해야 성능을 향상시킬 수 있을까요?
위에 내용을 보면 사실 Xcode가 알아서 최적화를 잘하긴 합니다만.. 더 잘할 수 있도록 도와주는 방법이 있습니다.
컴파일러가 파일을 하나하나 서로 연관이 있는지 없는지 살펴볼 때, 개발자가 미리 연관이 없다고 연결 고리를 끊어주면 컴파일러의 일이 줄어들고 최적화가 되겠죠?
먼저 Method Dispatch는 코드 실행 결정 시점에 따라 성능이 달라집니다. 당연히 런타임 때 보다 컴파일 때 결정되는게 성능이 좋겠죠?
클래스 같은 경우, 상속을 할 수 있고 클래스의 메서드들은 override를 할 수 있기 때문에 컴파일 시점엔 알 수 없습니다.
그렇다고 클래스를 안 쓸 수는 없으니깐 대신 구조체처럼 만들어주면 효과적인 최적화가 가능합니다. 바로 final과 private 키워드를 붙이면 됩니다. 생각보다 간단하죠?
상속 시킬 필요가 없는 클래스에는 final 키워드를 붙여줍시다!
override 할 필요가 없는 메서드는 private 키워드를 붙여줍시다!
final 키워드가 없으면 컴파일러는 혹시나 상속 시킬 수도 있다고 생각해서 여기저기 연결고리를 만들어두고, 갖고 있는 메서드 모두 override 될 수 있는 가능성으로 미리 생각해두기 때문에 컴파일 시점에서는 결정할 수 없는건데
final을 통해 더이상 상속할 수 없다고 명시하여 이를 미리 차단 시키면 최적화가 가능하여 생각보다 꽤 많이 성능상의 이점이 있습니다.
이는 구조체처럼 동작하는 Static Dispatch 방식으로 바꾸는 것과 마찬가지입니다.
private 키워드도 미리 접근을 제한하고 override를 막는 식으로 연결 고리를 차단하면 이 역시 static하게 동작할 수 있도록 만들기 때문에 최적화가 가능합니다.
최적화와 관련하여 WMO와 Swift 코드 성능 높이는 법을 알아보았는데요, 도움이 되었으면 좋겠습니다!