안녕하세요 :) 소들입니다~~!
오늘은 앞서 공부했던 Dispatch를 바탕으로 Swift 성능 최적화에 대해 공부해볼 거예요!
예전에 어디 머기업 개발자 컨퍼런스를 보는데,
클래스를 선언할 때, 상속되지 않는 클래스에는 final을 붙이면 성능이 향상된다
라는 것을 듣고 걍 모든 클래스마다 final을 붙이는 것을 생활화 했었어요
(일단 붙여놓고 나중에 상속이 필요할 경우 삭제해왔었음)
근데 그럼 왜 final을 붙일 때 성능이 향상되는지에 대해서는 제가 모르고 있더라구요 ㅎ;;
그래서 이번 포스팅에서 final을 붙이면 성능이 왜 향상될까!?에 대해 답을 찾아보려고 해요!
앞서 Dispatch에 대한 포스팅을 다 읽고 왔다면, 이미 답을 하실 수 있을 거라 생각하는데 ㉪
혹시 Swift의 Static Dispatch & Dynaminc Dispatch를 모르신다면
Swift) Static Dispatch & Dynamic Dispatch (1/2)
Swift) Static Dispatch & Dynamic Dispatch (2/2)
위 포스팅들을 차례대로 읽고와주시길 바랍니다 :D
그래야 이해할 수 있어요!
모든 포스팅은 편의 말투로 합니다~!!
1. Dynamic Dispatch의 성능상 문제점
앞서 Dispatch에 대해 설명할 때 Dynamic Dispatch는
런타임 시에 vTable을 찾아 메서드를 실행하기 때문에 성능상 손해를 본다고 했음
그럼 가장 대표적으로 Dynamic Dispatch로 동작하는 Class의 경우,
왜 Dynamic Dispatch로 동작한다고 했음!?
상속!
즉, 오버라이딩의 가능성!
때문임!
class Human {
func sayHello() {
print("Hello Human!")
}
}
class Teacher: Human { }
var sodeul: Human = Teacher()
sodeul.sayHello() // Hello Human!
|
sodeul이란 변수는
Human 타입임에도 불구하고 sayHello()란 메서드를 실행할 때
Human 클래스를 참조해야 할지,
혹은 오버라이딩 한 하위 클래스에서 참조해야 할지를
런타임 시점에 vTable이란 것을 통해 찾아야 해 오버헤드(overhead)가 발생한다고 했음!
이처럼, Swift에서 클래스는 상속을 지원하기 때문에 오버라이딩의 가능성이 있어서
Dynamic Dispatch로 동작함
내가 근데 지금껏 메서드에 대해서만 말해왔지만,
사실 Property 또한 오버라이딩이 가능하기 때문에
Property도 같이 Dynamic Dispatch로 동작함ㅎㅎ
따라서 성능이 중요한 코드에서 Dynamic Dispatch는 사실 바람직하지 않음ㅎㅎ;
만약 상속 가능성이 없는 클래스나, 오버라이딩이 되지 않는 프로퍼티와 메서드의 경우,
굳이 Dynamic Dispatch로 동작할 필요가 없으니
이를 Static Dispatch로 바꿔주는 방법이 필요할 거 같지 않음!?!?
그럼 이 Dynamic Dispatch를 이제 Static Dispatch로 바꿔주는 방법에 대해 세 가지 방법으로 알아볼 것임!
2. Static Dispatch를 이용한 성능 최적화 방법
2-1. 상속, 오버라이딩 될 필요가 없는 클래스, 메서드, 프로퍼티에 final 키워드 붙이기
드디어 final 등장!
swift에서 final의 역할이 뭐냐면
final은 클래스, 메서드, 프로퍼티 앞에 붙일 수 있는 키워드인데
final이 붙은 클래스의 경우 상속이 불가하고,
메서드, 프로퍼티에 붙을 경우 더이상 하위 클래스에서 오버라이딩을 할 수 없음
만약 다음과 같이 클래스에 final을 붙일 경우
final class Human {
var name: String = ""
func sayHello() {
print("Hello Human!")
}
}
class Teacher: Human { } // error! Inheritance from a final class 'Human'
|
이 때는 Human 자체가 상속이 불가능하기 때문에,
클래스 내의 모든 프로퍼티(name), 메서드(sayHello)가 Static Dispatch로 동작하게 됨!!!
만약 다음과 같이 프로퍼티, 메서드에 한해 final을 붙일 경우,
class Human {
final var name: String = ""
var age: Int = 0
final func sayHello() {
print("Hello Human!")
}
func sayHo() {
print("Ho~~!")
}
}
|
final 키워드가 붙은 프로퍼티(name), 메서드(sayHello)는
오버라이딩이 불가하여 Static Dispatch로,
final 키워드가 붙지 않은 프로퍼티(age), 메서드(sayHo)는
오버라이딩이 가능하므로 Dynamic Dispatch로 동작함
드디어 final을 왜 써야 하는지에 대해 알았다!!
2-2. 접근이 현재 파일로 제한되는 경우 private 키워드 붙이기
private 키워드를 붙일 경우, 참조할 수 있는 곳이 현재 파일로 제한되잖음?
따라서 컴파일러는 private 키워드가 참조될 수 있는 곳에서 오버라이딩이 되는지 안 되는지를 알아서 판단함
그리고 만약 오버라이딩 되는 곳이 없다면 스스로 final 키워드를 추론해서 Static Dispath로 동작한다고 함!!
이해를 돕기 위해 프로퍼티만 예를 보자면,
class Human {
private var name: String = ""
private var alias: String = ""
var age: Int = 0
class Sodeul: Human {
override var name: String {
didSet {
print("이름 바꼈다!")
}
}
}
}
|
여기서 private 키워드가 붙은 name, alias는 컴파일러가 오버라이딩이 될 수 있는지를 알아서 판단함
따라서 name이란 프로퍼티는 참조될 수 있는 곳(Sodeul)에서 오버라이딩을 하고 있으니,
이땐 Dynamic Dispatch로 동작하고
alias란 프로퍼티는 참조될 수 있는 곳에서 오버라이딩을 하고 있지 않으니
이땐 알아서 Static Dispatch로 동작함
private 키워드가 붙지 않은 age는 당연히 Dynamic Dispatch로 동작함
근데 name이란 키워드는 private이 붙었응께 외부에서 호출 못할텐데
오버라이딩 하는 거랑 Dynamic Dispatch로 동작하는 거랑 먼 상관임??
할 수도 있는데
(내가 처음에 그랬다고 한다..)
class Human {
private var name: String = ""
private var alias: String = ""
var age: Int = 0
class Sodeul: Human {
override var name: String {
didSet {
print("이름 바꼈다!")
}
}
}
func resetName() {
name = ""
}
}
var sodeul: Human = .init()
sodeul.resetName() // (Human의 name 호출)
sodeul = Human.Sodeul.init()
sodeul.resetName() // "이름 바꼈다!" (Sodeul의 name 호출)
|
이렇게 private으로 선언된 프로퍼티나 메서드를 더 상위 접근 제어자를 가진 곳인 resetName에서 사용할 경우,
resetName란 메서드를 호출했을 때
name이란 프로퍼티가 참조되어야 하는 타입이 다르기 때문에
Dynamic Dispatch로 동작한답니다 호호~~~
2-3. WMO(Whole Module Optimization) 사용하기
WMO 가 무엇이냐면, Xcode build Settings에 보면
Compliation Mode에 Release가 Whole Module로 되어 있다능
이건 따로 설정하지 않아도 Xcode 8부터 Release 일 때 자동으로 켜져 있음
그래서 Whole Module이 머냐면
모듈 전체를 하나의 덩어리로 컴파일 하여,
internal level 에 대해서 오버라이딩이 되는지 안 되는지를 추론 할 수있게 되고
오버라이딩이 되지 않을 경우, 내부적으로 final을 붙힌다
..? 무슨 말..?
이냐면, Swift는 기본적으로 컴파일을 할 때 모듈 내의 파일들을 하나 하나씩 컴파일 함
따라서 A라는 파일에서 Human이란 클래스를 정의하고,
B라는 파일에서 Human이란 클래스를 상속받는 Teacher 클래스를 정의했을 때
A라는 파일을 컴파일 할 때, Human 클래스가 B라는 파일에서 상속이 되는지 안되는지를
컴파일 시점에 알 수가 없음..!!!
따라서, 만약 다른 파일에서 상속받지 않을 경우 자동으로 final로 동작하고 싶어도,
파일을 하나씩 컴파일 하기 때문에 다른 파일에서 어떻게 쓰여질지에 대해 당최 알 수가 없음 ㅠ
근데 이를 극복한 것이 WMO라는 것인데, 이 WMO라는 것은
하나의 모듈을 컴파일 할 때 파일 하나하나씩이 아니라, 모듈 전체를 확인하며 컴파일 함
따라서 A라는 파일에서 Human 클래스를 정의했다면,
모듈 전체 파일을 확인하며 Human 클래스를 상속받는 클래스 혹은
해당 클래스의 메서드, 프로퍼티를 오버라이딩 하는 클래스가 있는지를 알아서 확인하고,
만약 아무곳에서도 상속(오버라이딩)하지 않으면 내부적으로 final을 붙여서 Static Dispatch로 동작하게끔 하는 것임!!
자, 근데 이것은 Swift 클래스의 기본 접근 제어자가 internal이기 때문에 가능한 것이고
만약 public, open 키워드를 붙일 경우, 외부 모듈에서도 접근할 수 있기 때문에
이때는 WMO를 사용하여도 Dynamic Dispatch로 동작한다고 함
근데.. open일 경우 외부 모듈에서 상속 및 오버라이딩이 가능하지만
public일 경우 접근은 가능하지만 상속 및 오버라이딩은 불가능 하니까 public까진 허용 되어야 하는 거 아닌가..?.?
에 대한 의문은 남긴 채ㅣ...........
open class Human {
open var name: String = ""
internal var alias: String = ""
}
|
쨌든 뭐 난 public이 왜 허용 안 되는지 이해 못했으니까 open으로 예를 보면
접근 제어자가 open인 name 프로퍼티는 외부 모듈에서 오버라이딩 할 가능성이 있어
WMO를 켜도 Dynamic Dispatch로 동작하고,
internal로 선언된 프로퍼티 alias은 WMO를 켤 경우
내부적으로 오버라이딩 되지 않으면 알아서 final이 붙어 Static Dispatch로 동작한다 함
.
.
.
흠... 마지막 의문은 알게 되면 추가하겠슴돠
Swift에서 클래스보다 구조체를 더 장려하잖아여?
그 이유가 물론 메모리 관점의 성능 차이도 있겠지만, 이렇게 Dispatch에 의한 성능차이도 있지 않을까..
란 생각을 해봤는데 생각해보니 구조체에서 상속이 없는 대신 다 Protocol로 구현하라 하잖음..?
(Swift가 프로토콜 지향 프로그래밍이기도 하고..)
근데 Protocol은 또 Dynamic Dispatch로 동작하늗네... ^^,,;;;;; 아닌가보다
쨌든 이제 final을 붙일 때 왜 붙여야 하는지 알면서 할 수 있을 거 같아 좋네연~
혹시 잘못된 내용이나 오타, 질문 사항이 있으시면 언제든 댓글로 남겨주세요 :)!!!
+ 마지막 의문에 대해 댓글로 알려주셔서 추가 하겠습니당
swift2일 땐 public이 지금의 open이랑 같았어서,
public이 안된다!! 이렇게 정보가 도는 것 같은 느낌이 드시지 않나들
제 생각엔 아마 public까진 WMO가 적용될 듯 합니당!
누군가 아니라 생각이 들면 또 답글 달아주시길 바람
'iOS > Swift' 카테고리의 다른 글
Swift) Equatable에 대해 알아보자 (12) | 2022.01.14 |
---|---|
Swift5+) @unknown(Unfrozen Enumertaion) (10) | 2021.12.03 |
Swift) Static Dispatch & Dynamic Dispatch (2/2) (1) | 2021.10.13 |
Swift) Static Dispatch & Dynamic Dispatch (1/2) (9) | 2021.10.08 |
Swift) Lottie를 이용해 애니메이션을 그려보자 (13) | 2021.08.17 |