struct + protocol 를 사용하면 단순히 struct를 사용하는 것에 비해 성능이 안좋아진다고 했는데 이 타입을 파라미터로 전달한다고 가정했을 때 어떻게 하면 성능이 좋을 수 있을까?
정답은 제네릭함수 사용하는 것!
제네릭함수 사용하지 않았을 때
```swift
// Drawing a copy
protocol Drawable {
func draw()
}
func drawACopy(local : Drawable) {
local.draw()
}
let line = Line()
drawACopy(line)
// ...
let point = Point()
drawACopy(point)
```
제네릭함수 사용했을 때
```swift
// Drawing a copy using a generic method
protocol Drawable {
func draw()
}
func drawACopy(local : T) {
local.draw()
}
let line = Line()
drawACopy(line)
// ...
let point = Point()
drawACopy(point)
```
그냥 프로토콜 타입으로 전달한 것 VS 제네릭타입을 써서 제네릭함수로 정의한 것
이 부분을 중점적으로 비교해서 제네릭함수를 사용했을 때 대체 왜 성능이 좋아지는 알아보자
parametric polymorphism, static form of polymorphism
Generic code는 더욱 정적인 형태의 polymorphism을 구현할 수 있도록 도와줍니다. (static form of polymorphism, 이것을 parametric polymorphism이라고도 부른다)
protocol을 이용했을 때에는 struct를 사용하더라도 PWT를 사용하는 등 메서드가 동적(Dynamic Dispatch)으로 결정되는 부분이 있었는데, generic에서는 그렇게 동작하지 않는다는 뜻
제네릭 함수는 호출하는 시점에 파라미터(로컬 변수)의 타입이 바인딩 됨
foo, bar function의 예시
foo를 호출했을 때 local 변수의 타입이 Point 로 특정된다
→ foo내부에서 bar을 호출하는데, 이 시점에 bar 함수의 generic type인 T를 Point로 binding한다.
이런 방식으로 타입은 call chain으로 대체되어 내려간다. (Type substituted down the call chain)
이 과정이 바로 parametric polymorphism, static form of polymorphism
이처럼 제네릭 함수를 호출하면 호출하는 시점에 타입이 바인딩되고 그로인해 내부에서는 특정된 타입이 사용되면서 dynamic(동적)이 아닌 static(정적)으로 동작할 수 있는 것이다!
Swift가 내부적으로 static polymorphism을 구현하는 방법
이게또… 제네릭 함수 한 번의 호출에 하나의 타입만 전달(One type per call context)하는 경우에만 해당되네..??
한 번의 호출에 하나의 타입만 전달(One type per call context)이거에 집중해야해!
이 경우에는 existential container를 사용하지 않는다
그럼 existential container없이 어떻게 메모리를 할당하고 내부 메서드 실행하는가?
PWT/VWT는 추가 인자로 전달
로컬 변수를 파라미터로서 만들고 값을 저장시키려면 VWT의 allocate을 통해 stack에 할당을 해야한다
추가적인 인자로 PWT, VWT 를 함께 전달하기 때문에 local.draw()를 호출할 때에도 전달된 PWT를 참조하여 호출
발표자료에서는 저장프로퍼티를 저장하는데 이 사진을 썼는데..
existential container 없다고 했고 valueBuffer 도 없을텐데 왜 이렇게 표현했는지 모르겠고,뒤에서 나오는 그림에서는 값의 크기에 상관없이 다 stack 내부에 저장하는데 왜 저런 자료를 보여줬는지 잘 모르겠다..
그래서 이게 제네릭 안 쓴 버전보다 빠른게 확실해? Is this any faster? Is this any better?
static form of polymorphism은 제네릭의 specialization이라고 불리는 컴파일러 최적화를 가능하게한다.
그래서 specialization 가 뭔데? 어떻게 하는건데?
함수 호출 시점에 특정 타입으로 바인딩 되고(static form of polymorphism)
그 타입을위한 특정 버전의 메서드를 생성함(specialization)
이렇게만 보면 코드 사이즈가 증가한다고 생각할 수 있지만
But, because the static typing information that is now available enables aggressive compiler optimization, Swift can actually reduce the code size here.
정적으로 입력된 정보(전달된 타입별로 새롭게 생성된 메서드들)는 더 적극적인 최적화(aggressive optimization)를 가능하게 하고 결과적으로는 코드 사이즈 줄임!!
→ this can be even further reduced to the implementation of draw
& drawACopyOfAPoint method is no longer referenced → compiler remove it
specialize하기 위해선 call-site에서 타입을 추론할 수 있어야한다. (infer the type at this call-site)
specialization 중에 사용될 타입과 제네릭 함수의 정의가 있어야한다. (Definition must be available)
struct 정의, 정의한 struct 타입에 대한 인스턴스를 저장한 로컬 변수, 그 변수를 인자로서 넘겨주는 함수의 실행이 모두 한 파일에 존재할수도 다른 파일에 존재할 수도 있는데
한 파일에 존재한다면
다른 파일에서 존재한다면
Whole Module Optmizatione(WMO)가 더욱 활발하게 일어날 수 있음
컴파일러가 두 파일을 따로 컴파일 할 것이기 때문에 WMO(Whole Module Optimization)을 통해서 모든 파일을 하나의 단위로서 컴파일될 수 있고 최적화가 가능
WMO는 Xcode 8부터 기본값으로 활성화 되어있다고 한다.
Pair 구조체 initializer 관련 예시코드
Pair (제네릭 타입이 아닌 initializer)
코드
```swift
protocol Drawable {
func draw()
}
struct Point : Drawable {
var x, y: Double
func draw() {
}
}
struct Line : Drawable {
var x1, y1, x2, y2: Double
func draw() {
}
}
// Pairs in our program
struct Pair {
init(_ f: Drawable, _ s: Drawable) {
first = f ; second = s
}
var first: Drawable
var second: Drawable
}
let pairOfLines = Pair(Line(), Line())
let pairOfPoint = Pair(Point(), Point())
```
Line 인스턴스 값을 저장하기 위해서는 valueBuffer의 용량을 넘어가기 떄문에 heap에 저장되어야할 것이다.
Pair(Line(), Line())가 실행되면서 Line 인스턴스 값을 저장하기 위해 두 번의 힙 할당이 일어날 것이다.
Pair (제네릭 타입인 initializer)
코드
```swift
// Pairs in our program using generic types
struct Pair {
init(_ f: T, _ s: T) {
first = f ; second = s
}
var first: T
var second: T
}
let pairOfLines = Pair(Line(), Line())
// ...
let pairOfPoint = Pair(Point(), Point())
```
Pair이라는 struct의 이니셜라이저가 제네릭 타입으로 정의되어 있으며
첫번째 파라미터와 두번째 파라미터의 타입을 똑같은 것으로 강제함으로써 같은 타입만 넣을 수 있도록 강제해놨다.
⇒ 우리가 위에서 살펴봤던대로 제네릭 함수의 실행에 하나의 타입만 전달되는 경우(One type per call context)에 부합하는 것!
자 그럼 앞에서 우리가 봤던 제네릭 함수처럼 프로토콜을 준수하는 구조체 인스턴스를 제네릭 initializer에 넘겨주었을 때 무슨일이 일어나는지 다시한 번 살펴보자
하나의 타입만 받는 제네릭 함수는 static polymorphism, parametric polymorphism 으로 동작하며
프로토콜을 준수하는 구조체 인스턴스를 함수 내 로컬 변수로 사용해도 제네릭함수이기 때문에 Existential Container로 저장되지 않는다. 즉, 인스턴스 별로 valueBuffer/PWT참조/VWT 참조를 저장하는 stack영역에 저장된 컨테이너가 없는 것!
대신에 함수 실행 시 argument와 함께 pwt, vwt를 추가 인자로 직접 전달하다고 했다?!
그럼 Existential Container가 없으면 값은 어떻게 저장하냐? - 프로토콜 채택하지 않는 보통의 구조체 처럼 stack에 저장한다.
메서드 실행이 static정적으로 이루어진다. 이 말은 ! 이 경우에는 제네릭함수가 실행될 때 specialization를 통해 전달된 타입 버전으로 함수를 만들기 때문에 굳이 해당 메소드가 누구껀지 저장할 pwt가 필요 없고 → static Disptatch 해버리면된다는 것!
이것도 WWDC 2016 Understanding Swift Performance 세션과 연관되는 글이다.
struct + protocol 를 사용하면 단순히 struct를 사용하는 것에 비해 성능이 안좋아진다고 했는데 이 타입을 파라미터로 전달한다고 가정했을 때 어떻게 하면 성능이 좋을 수 있을까? 정답은 제네릭함수 사용하는 것!
제네릭함수 사용하지 않았을 때
제네릭함수 사용했을 때
parametric polymorphism, static form of polymorphism
foo, bar function의 예시
Swift가 내부적으로 static polymorphism을 구현하는 방법
이게또… 제네릭 함수 한 번의 호출에 하나의 타입만 전달(One type per call context)하는 경우에만 해당되네..??
이 경우에는 existential container를 사용하지 않는다
그럼 existential container없이 어떻게 메모리를 할당하고 내부 메서드 실행하는가?
PWT/VWT는 추가 인자로 전달
로컬 변수를 파라미터로서 만들고 값을 저장시키려면 VWT의 allocate을 통해 stack에 할당을 해야한다
추가적인 인자로 PWT, VWT 를 함께 전달하기 때문에 local.draw()를 호출할 때에도 전달된 PWT를 참조하여 호출
발표자료에서는 저장프로퍼티를 저장하는데 이 사진을 썼는데..
existential container 없다고 했고 valueBuffer 도 없을텐데 왜 이렇게 표현했는지 모르겠고,뒤에서 나오는 그림에서는 값의 크기에 상관없이 다 stack 내부에 저장하는데 왜 저런 자료를 보여줬는지 잘 모르겠다..
static form of polymorphism은 제네릭의 specialization이라고 불리는 컴파일러 최적화를 가능하게한다.
그래서 specialization 가 뭔데? 어떻게 하는건데?
specialization은 언제, 어느조건에서 발생해? Whole Module Optmizatione(WMO)는 뭐야?
아래 조건 두 가지가 만족해야 specialization 발생
struct 정의, 정의한 struct 타입에 대한 인스턴스를 저장한 로컬 변수, 그 변수를 인자로서 넘겨주는 함수의 실행이 모두 한 파일에 존재할수도 다른 파일에 존재할 수도 있는데
Pair 구조체 initializer 관련 예시코드
Pair (제네릭 타입이 아닌 initializer)
코드
Pair (제네릭 타입인 initializer)
코드
Pair이라는 struct의 이니셜라이저가 제네릭 타입으로 정의되어 있으며
첫번째 파라미터와 두번째 파라미터의 타입을 똑같은 것으로 강제함으로써 같은 타입만 넣을 수 있도록 강제해놨다.
⇒ 우리가 위에서 살펴봤던대로 제네릭 함수의 실행에 하나의 타입만 전달되는 경우(One type per call context)에 부합하는 것!