[WWDC 2016] Understanding Swift Performance - Protocol (2)
WWDC 2018 Understanding Swift Performance 를 정리한 포스트입니다.
더 자세한 내용을 원하시면 위 링크를 참조하시길 바랍니다.
1. [WWDC 2016] Understanding Swift Performance - Struct, Class (1)
2. [WWDC 2016] Understanding Swift Performance - Protocol (2)
2. [WWDC 2016] Understanding Swift Performance - Generic (3)
Protocol
abstract Class 대신 draw 함수를 선언하는 protocol Drawable 이 있습니다.
Protocol Witness Table
draw 함수를 dispatch 하기 위해선
abstract Class 는 V-Table 을 사용하였지만
protocol 은 Protocol Witness Table 을 사용합니다.
응용 프로그램에서 프로토콜을 구현하는 유형별 테이블 중 하나가 있습니다.
그리고 해당 테이블의 항목은 유형의 구현에 연결됩니다.
Type 별로 Table 이 존재하며 해당 테이블의 항목은 Type의 구현에 연결됩니다.
Protocol Witness Table for Array
우리는 2가지 질문이 있습니다.
1. 배열의 요소에서 테이블로 어떻게 가야합니까?
2. 배열의 고정 오프셋에서 요소를 균일하게 저장하는 방법은 무엇입니까?
The Existential Container
class Line {
let x1: Double
let y1: Double
let x2: Double
let y2: Double
}
class Point {
let x: Double
let y: Double
}
(참조는 기본적으로 하나의 words)
Line 4 words 필요
Point 2 words 필요
Word 에 대한 자세한 설명은 여기를 참조해주세요.
Swift가 Existential Container라는 특수 저장소 레이아웃을 사용합니다.
three words는 valueBuffer 용으로 예약되어 있습니다.
Point 는 2 words가 필요하지만 Line 은 4 words 가 필요합니다.
Swift는 힙에 메모리를 할당하고 값을 저장하고
해당 컨테이너에 대한 포인터를 기존 컨테이너에 저장합니다.
Point, Line 사이에 차이가 있으며 Existential Container 는 두 차이를 관리해야 합니다.
The Value Witness Table (VWT)
Allocation, Copy, Destruction of any Value
Value Witness Table은 변수의 lifetime을 관리하며
프로그램에서 Type 별로 테이블이 존재합니다.
Allocate
Protocol Type의 Local 변수 lifetime이 시작될 때
Swift는 해당 테이블 내부에서 Allocate 함수를 호출합니다.
Allocate 함수는 Line Value Witness Table 을 가지고 있기 때문에
힙에 메모리를 할당하고 Existential Container의 valueBuffer 안에 해당 메모리에 대한 포인터를 저장합니다.
Copy
Swift는 로컬 변수를 초기화하는 할당 소스의 값을 Existential Container로 복사해야 합니다.
힙에 할당 된 valueBuffer에 복사합니다.
Destruct
지역 변수 수명이 끝나면
destruct 함수를 호출하여 Type에 포함될 수있는 값의 참조 카운트를 줄입니다.
Deallocate
Line The Value Witness Table (VWT) 값에 대한 힙 할당된 메모리를 해제합니다.
The Value Witness Table (VWT) Reference
Existential Container에는 The Value Witness Table (VWT)에 대한 참조가 있습니다.
Existential Container Example
Existential Container는 valueBuffer에 대한 3개 words (Int, Int, Int) 저장 공간
Value Witness Table (VWT)
Protocol Witness Table
구성하는 구조체가 있습니다.
drawACopy 함수가 실행될 때 Parameter를 수신하여 함수에 전달합니다.
Generated Code에서 Parameter의 Existential Container를 해당 함수에 전달합니다.
함수가 실행되면 해당 Parameter에 대한 로컬 변수를 만들고 인수를 지정합니다.
따라서 Swift는 Existential Container를 힙에 할당합니다.
Existential Container에서 The Value Witness Table (VWT)과 프로토콜 감시 테이블 Protocol Witness Table(PWT).을 읽고 필드를 초기화합니다.
그 다음 필요한 경우 버퍼를 할당하고 값을 복사하기 위해 Allocate (VWT) 함수을 호출합니다.
(Point 는 2 words 이므로 동적 힙 할당이 필요하지 않습니다.)
Line은 4 words 이기 때문에 힙 할당이 됩니다.
Line 에 대한 valueBuffer를 할당하고 값을 복사합니다. Copy (VWT) 함수
draw 메소드가 실행되고 Swift는 Existential Container의 필드에서 Protocol Witness Table(PWT)을 찾고
해당 테이블의 고정 오프셋에서 draw 메소드를 찾고 구현으로 이동합니다.
pwt.draw(vwt.projectBuffer(&local)) 은 무엇을 의미할까요?
pwt.draw 인자의 주소값을 의미합니다.
draw 메소드 함수가 종료되면 Swift는 The Value Witness Table (VWT) 의 destruct 함수를 호출하고 값을 제거합니다.
값에 참조가 있으면 참조 카운트를 줄이고 버퍼가 할당 된 경우 버퍼를 할당 해제합니다.
함수 실행이 끝나고 스택이 제거되어 스택에서 생성 된 로컬 Existential Container가 제거됩니다.
Protocol Type Stored Properties
Drawable first, second protocol의 struct Pair 가 있습니다.
Pair을 할당 할 때 Swift는 해당 Pair을 인 클로징 구조체의 인라인으로 저장하는 데 필요한 두 개의 Existential Container를 저장합니다.
Line과 Point의 Pair을 초기화하고, Line에 대해서는 힙에 버퍼를 할당합니다.
Point는 Inline valueBuffer에 맞으며 Existential Container-inline에 저장할 수 있습니다.
두 개의 힙 할당을 Copy 합니다.
"4개의 힙 할당 비용이 비싼가요?
Existential Container는 3 개의 words를위한 장소를 가지고 있으며,
참조는 기본적으로 하나의 words이기 때문에 3 개의 words에 대한 참조가 적합합니다.
Protocol 최적화?
Small | Large |
3개의 참조를 유지하면 힙 할당이 발생하지 않습니다. (Fits in Value Buffer: no heap allocation) |
4개 이상 참조를 유지하면 힙 할당이 발생합니다. (Heap allocation) |
구조체에 참조가 없으면 참조 카운트도 없습니다. (No reference counting)
|
참조 카운트 오버 헤드도 발생합니다. (Reference counting if value contains references) |
Protocol Witness Table(PWT) 간접적인 접근을 통해 동적으로 다형성 동작이 가능합니다. (Dynamic dispatch through Protocol Witness Table) |