WWDC 2016 Understanding Swift Performance
Understanding Swift Performance - WWDC 2016 - Videos - Apple Developer
In this advanced session, find out how structs, classes, protocols, and generics are implemented in Swift. Learn about their relative...
developer.apple.com
위 WWDC 자료를 보고 궁금한 점이 생겨, 알아본 내용입니다.
해당 WWDC의 주 내용은 Struct와 Protocl 그리고 Generic을 사용하여 Protocol 타입을 사용할 경우 Generic과 함께 사용하여 Existential Container를 생성하지 않고, Concrete 한 타입으로 Static Dispatch를 유도하고 Heap 할당을 최소화하여 성능을 개선시킬 수 있는 것이라고 생각합니다.
주로 내용이 Struct + Protocol / Class + Inheritence 위주의 비교였고, Class + Protocol에 대해선 간략하게 나와 있어 이 점에 대해 의문이 들었습니다.
- Class의 경우 Existential Container는 Stack or Heap 중에 어디에 할당이 되는가?
- Class의 경우 inline value buffer의 의미가 있는가? (어짜피 Heap 할당인데,,)
- Class의 경우 Generic을 사용해도 Static Dispatch가 일어나지 않는가?
[메모리 주소를 출력하기 위한 간단한 Helper]
/// BEGIN Memory 주소 출력 Helper | |
func address(o: UnsafeRawPointer) -> Int { | |
return Int(bitPattern: o) | |
} | |
func address<T: AnyObject>(o: T) -> Int { | |
return unsafeBitCast(o, to: Int.self) | |
} | |
func addressString(_ value: Int) -> String { | |
return String(format: "%d", value) | |
} | |
/// END |
[Protocol 할당 위치 Test]
/// BEGIN Sample Model | |
protocol CC {} | |
class ClassA: CC { | |
let value: Int | |
init(value: Int) { | |
self.value = value | |
} | |
} | |
/// END Sample Model | |
var c1 = ClassA(value: 1) | |
var c2 = ClassA(value: 2) | |
var p: CC = ClassA(value: 3) | |
var c3 = ClassA(value: 4) | |
let c1Addr = addressString(address(o: &c1)) | |
let c2Addr = addressString(address(o: &c2)) | |
let pAddr = addressString(address(o: &p)) | |
let c3Addr = addressString(address(o: &c3)) | |
print(c1Addr) | |
print(c2Addr) | |
print(pAddr) | |
print(c3Addr) | |
// 22545728 | |
// 22545736 | |
// 22545744 | |
// 22545784 |
Class 타입의 경우에는 Stack에 Heap에 대한 메모리 주소만 가지고 있으며, 이는 한 워드(8바이트)로 되어있습니다. (64bit 기준)
따라서 Class 타입의 각 변수들은 8바이트씩 차이가 나는 것을 볼 수 있는데, Protocol 타입의 경우 40바이트를 차지하고 있는 것을 볼 수 있었습니다.
여기서 1번에 대한 해답을 얻을 수 있었습니다. Struct, Class와 관계 없이 Protocol 타입은 Stack에 할당되며, 크기는 40바이트로 고정된 크기를 가지고 있습니다.
여기에서 40바이트의 의미는 3-word의 inline value buffer(24), vwt(8), pwt(8)의 크기를 가진 Existential Container로 사이즈입니다.
[일반 프로토콜과 클래스 전용 프로토콜 비교]
protocol ClassOnlyProtocol: AnyObject {} | |
protocol AnyProtocol {} | |
print(MemoryLayout<ClassOnlyProtocol>.size) | |
print(MemoryLayout<AnyProtocol>.size) | |
// 16 | |
// 40 |
class-only 프로토콜의 경우에는 Protocol 크기가 16바이트로 고정되어 있는 것을 알 수 있습니다.
이는 Class 타입의 경우 Heap에 할당이 되기 때문에, Heap에 대한 reference가 불가피하게 필요하므로 inline value buffer가 필요하지 않아 3-word 만큼 줄어든 크기인 16바이트라고 생각합니다.
이는 class-only 프로토콜의 경우 inline value buffer가 실질적으로 필요하지 않기 때문에 제거한 요소라고 생각할 수 있었습니다.

Generic으로 하여 Concrete한 타입이 지정되기 때문에 Protocol의 Existential Container에 따른 Dynamic dispatch는 막을 수 있지만, 상속, V-Table에 따른 dynamic dispatch는 진행되는 것을 알 수 있었습니다.
'Swift' 카테고리의 다른 글
[Swift] What's New in Swift (5.0/5.1) (1) | 2020.02.29 |
---|---|
[Swift 5.1] Property Wrapper 개념과 사용 예시, 그리고 한계점. (1) | 2020.02.29 |