4. Value Semantics
Value Type Semantics / Copy-by-Value Semantics
Identity가 아닌 Value(값)에만 의미를 둔다
• Int, Double 등의 기본 타입들
포인터만 복사되는 참조(Reference) 시맨틱스와 비교됨
• Objective-C, Java 등
스위프트엔 Objc에 없던 새로운 Value Type을 도입
• struct, enum, tuple
5. Value Type의 특징
변수 할당 시 Stack에 값 전체가 저장됨
다른 변수에 할당될 때 전체 값이 복사됨 (copy by value)
• 변수들이 분리됨: 하나를 변경해도 다른 것에 영향 없음
Heap을 안 쓰며 따라서 Reference Counting도 필요 없음
6. class vs struct
class Point {
var x: CGFloat
var y: CGFloat
}
var c1 = Point(x: 0.0, y: 0.0)
var c2 = c1
c2.y = 2.0
7. class vs struct
class Point {
var x: CGFloat
var y: CGFloat
}
var c1 = Point(x: 0.0, y: 0.0)
var c2 = c1
c2.y = 2.0
Stack
c1: ref
Heap
…
refCount: 1
x: 0.0
y: 0.0
8. class vs struct
class Point {
var x: CGFloat
var y: CGFloat
}
var c1 = Point(x: 0.0, y: 0.0)
var c2 = c1
c2.y = 2.0
Stack
c1: ref
c2: ref
Heap
…
refCount: 2
x: 0.0
y: 0.0
9. class vs struct
class Point {
var x: CGFloat
var y: CGFloat
}
var c1 = Point(x: 0.0, y: 0.0)
var c2 = c1
c2.y = 2.0
// c1.y == 2.0
// c2.y == 2.0
Stack
c1: ref
c2: ref
Heap
…
refCount: 2
x: 0.0
y: 2.0
Reference 타입은 하나의 Identity
변수가 Copy되어도 값이 하나를 향해 같은 값을 가진다
10. class vs struct
struct Point {
var x: CGFloat
var y: CGFloat
}
var c1 = Point(x: 0.0, y: 0.0)
var c2 = c1
c2.y = 2.0
11. class vs struct
struct Point {
var x: CGFloat
var y: CGFloat
}
var c1 = Point(x: 0.0, y: 0.0)
var c2 = c1
c2.y = 2.0
Stack
c1: x: 0.0
y: 0.0
12. class vs struct
struct Point {
var x: CGFloat
var y: CGFloat
}
var c1 = Point(x: 0.0, y: 0.0)
var c2 = c1
c2.y = 2.0
Stack
c1: x: 0.0
y: 0.0
c2: x: 0.0
y: 0.0
13. class vs struct
struct Point {
var x: CGFloat
var y: CGFloat
}
var c1 = Point(x: 0.0, y: 0.0)
var c2 = c1
c2.y = 2.0
// c1.y == 0.0
// c2.y == 2.0
Stack
c1: x: 0.0
y: 0.0
c2: x: 0.0
y: 2.0
Value 타입의 각자의 변수는 Copy되어도 분리되어있다
14. Value Semantics: ’값’에 의해 구분됨
Value semantics에서는 Identity가 아니라 Value가 중요하다.
각 변수는 값(Value)에 의해 구분이 되어야한다.
따라서 동치 관계여야 한다.
-> 간단합니다, Equatable을 구현하세요
(단순히 데이터를 전달할 목적인 struct 변수를 말하는 것이 아님)
17. Value Type과 Thread
var numbers = [1, 2, 3, 4, 5, 6, 7, 8]
scheduler.processNumbersAsync(numbers)
for i in 0..<numbers.count { numbers[i] = numbers[i] + 1 }
scheduler.processNumbersAsync(numbers)
Copy value
Copy value
Thread간 의도하지 않은 공유로부터 안전함!
19. Copy는 빠르다
기본 타입들, enum, tuple, struct
• 정해진 시간 (constant time) 안에 끝남
내부 데이터가 Heap과 혼용하는 struct의 경우
• 정해진 시간 + 레퍼런스 copy등의 시간
• String, Array, Set, Dictionary 등
• 쓰기 시 Copy-on-write로 속도 저하 보완
23. Mutable이 효율적인 경우
func makeURL(subDirectories: [String]) -> NSURL? {
var array: NSArray = [NSHomeDirectory()]
for dir in subDirectories {
array = array.arrayByAddingObject(dir)
}
return NSURL.fileURLWithPathComponents(array as! [String])
}
계속 새로 개체를 생성하여 할당하고
String을 copy함
Objc에서 많이 쓰던 Immutable 방식
비효율적이다
24. Mutable이 효율적인 경우
func makeURL(subDirectories: [String]) -> NSURL? {
var array: NSMutableArray = [NSHomeDirectory()]
for dir in subDirectories {
array.addObject(dir)
}
return NSURL.fileURLWithPathComponents(array as! [String])
}
Mutable로 바꾸자
25. Mutable이 효율적인 경우
func makeURL(subDirectories: [String]) -> NSURL? {
var array: [String] = [NSHomeDirectory()]
for dir in subDirectories {
array.append(dir)
}
return NSURL.fileURLWithPathComponents(array)
}
Swift의 Array를 쓰면
26. API가 이상해지는 경우도
// 속도가 변경되었다
// Mutable + Value Type
car.dashboard.speed = 99
// Immutable + Reference Type
car.dashboard = Dashboard(speed: 99, rpm: car.dashboard.rpm)
Heap과 Reference Counting
또 컴파일러 최적화가 어려움
27. API가 이상해지는 경우도
// 속도가 변경되었다
// Mutable + Value Type
car.dashboard.speed = 99
// Immutable + Reference Type
car.dashboard = Dashboard(speed: 99, rpm: car.dashboard.rpm)
그것도 그렇지만,
Dashboard를 바꾼 다는 의미인건가?
28. 그래도 class도 중요한 경우
Value보단 Identity가 중요한 경우
• UIView 같이 모든 변수에서 단 하나의 state를 갖는 개체
OOP 모델
• 여전히 상속은 아주 훌륭한 도구
Objective-C 연동
Indirect storage (특수한 경우 struct내의 간접 저장소 역할)
• 뒤에서 설명
32. Heap 할당의 문제
할당시에 빈 곳을 찾고 관리하는 것은 복잡한 과정
무엇보다 그 과정이 thread safe해야한다는 점이 가장 큰 문제
• lock 등의 synchronization 동작은 큰 성능 저하 요소
반면 Stack 할당은
• 단순히 스택포인터 변수값만 바꿔주는 정도
33. enum Color { case red, green, blue }
enum Theme { case eat, stay, play}
var cache = [String: UIImage]()
func makeMapMarker(color: Color, theme: Theme, selected: Bool) -> UIImage {
let key = "(color):(theme):(selected)"
if let image = cache[key] { return image }
…
}
Heap 할당 줄이기
매번 Heap 할당
매우 빈번히 호출된다면 성능에 영향을 미칠 수 있다
(예를 들면 매우 큰 Loop안에서 일어나는 경우)
-> Key를 Value type으로 바꿔보자!
34. Heap 할당 줄이기
struct Attribute {
var color: Color
var theme: Theme
var selected: Bool
}
새로운 Key 타입 정의
35. Heap 할당 줄이기
struct Attribute: Hashable {
var color: Color
var theme: Theme
var selected: Bool
}
func ==(lhs: Attribute, rhs: Attribute) -> Bool {
return lhs.color == rhs.color &&
lhs.theme == rhs.theme &&
lhs.selected == lhs.selected
}
extension Attribute {
var hashValue: Int {
return [color.hashValue, theme.hashValue, selected.hashValue].hashValue
}
}
Dictionary의 Key가 되려면
36. enum Color { case red, green, blue }
enum Theme { case eat, stay, play}
var cache = [String: UIImage]()
func makeMapMarker(color: Color, theme: Theme, selected: Bool) -> UIImage {
let key = "(color):(theme):(selected)"
if let image = cache[key] { return image }
…
}
Heap 할당 줄이기
struct Attribute: Hashable {
var color: Color
var theme: Theme
var selected: Bool
}
37. enum Color { case red, green, blue }
enum Theme { case eat, stay, play}
var cache = [Attribute: UIImage]()
func makeMapMarker(color: Color, theme: Theme, selected: Bool) -> UIImage {
let key = Attribute(color: color, theme: theme, selected: selected)
if let image = cache[key] { return image }
…
}
Heap 할당 줄이기
struct Attribute: Hashable {
var color: Color
var theme: Theme
var selected: Bool
}
Value Type
Stack에서만 메모리 할당
Heap 할당 오버헤드 없음
38. Reference Counting의 문제
정말 자주 실행된다
• 변수 Copy할 때 마다
그러나 이것도 역시 가장 큰 문제는 thread safety 때문
• 카운트를 Atomic하게 늘리고 줄여야함
39. class MyClass { }
func foo(c: MyClass) {
…
}
do {
let c0: MyClass = MyClass()
var c1: MyClass? = c0
foo(c0)
c1 = nil
}
Reference Counting의 동작
40. class MyClass { }
func foo(c: MyClass) {
…
}
do {
let c0: MyClass = MyClass()
var c1: MyClass? = c0
foo(c0)
c1 = nil
}
Reference Counting의 동작
MyClass
Ref Count: 1
Heap
c0
41. class MyClass { }
func foo(c: MyClass) {
…
}
do {
let c0: MyClass = MyClass()
var c1: MyClass? = c0
retain(c1)
foo(c0)
c1 = nil
}
Reference Counting의 동작
MyClass
Ref Count: 2
c0
c1
Heap
42. class MyClass { }
func foo(c: MyClass) {
retain(c)
…
}
do {
let c0: MyClass = MyClass()
var c1: MyClass? = c0
retain(c1)
foo(c0)
c1 = nil
}
Reference Counting의 동작
MyClass
Ref Count: 3
c0
c1
Heap
c
43. class MyClass { }
func foo(c: MyClass) {
retain(c)
…
release(c)
}
do {
let c0: MyClass = MyClass()
var c1: MyClass? = c0
retain(c1)
foo(c0)
c1 = nil
}
Reference Counting의 동작
MyClass
Ref Count: 2
c0
c1
Heap
c
44. class MyClass { }
func foo(c: MyClass) {
retain(c)
…
release(c)
}
do {
let c0: MyClass = MyClass()
var c1: MyClass? = c0
retain(c1)
foo(c0)
c1 = nil
release(c1)
}
Reference Counting의 동작
MyClass
Ref Count: 1
c0
c1
Heap
45. class MyClass { }
func foo(c: MyClass) {
retain(c)
…
release(c)
}
do {
let c0: MyClass = MyClass()
var c1: MyClass? = c0
retain(c1)
foo(c0)
c1 = nil
release(c1)
release(c0)
}
Reference Counting의 동작
MyClass
Ref Count: 0
c0
Heap
46. class MyClass { }
func foo(c: MyClass) {
retain(c)
…
release(c)
}
do {
let c0: MyClass = MyClass()
var c1: MyClass? = c0
retain(c1)
foo(c0)
c1 = nil
release(c1)
release(c0)
}
Reference Counting의 동작
이것이 ARC
Automatic Reference Counting
손으로 다 넣던 시절이 있었습니다…
47. class MyClass { }
func foo(c: MyClass) {
retain(c)
…
release(c)
}
do {
let c0: MyClass = MyClass()
var c1: MyClass? = c0
retain(c1)
for _ in 1...100_000 { foo(c0) }
c1 = nil
release(c1)
release(c0)
}
Reference Counting의 동작
Loop는 프로그래밍의 기본
Ref Count 매우 빈번한 것
48. Method Dispatch (Static)
컴파일 시점에 메소드의 실제 코드 위치를 안다면
실행중 찾는 과정 없이 바로 해당 코드 주소로 점프할 수 있음
컴파일러의 최적화, 메소드 인라이닝 (Inlining) 가능
49. 메소드 인라이닝
컴파일 시점에 메소드 호출 부분에 메소드 내용을 붙여넣음
• 효과가 있다고 판단되는 경우에만
Call stack 오버헤드 줄임
• CPU icache나 레지스터를 효율적으로 쓸 가능성
컴파일러의 추가 최적화 가능
• 최근 메소드들이 작으므로 더더욱 기회가 많음
• 루프 안에서 불리는 경우 큰 효과
50. struct Point {
var x, y: CGFloat
func draw() {
// Point.draw implementation
}
}
func drawAPoint(param: Point) {
param.draw()
}
let point = Point(x: 0, y: 0)
drawAPoint(point)
메소드 인라이닝
51. struct Point {
var x, y: CGFloat
func draw() {
// Point.draw implementation
}
}
func drawAPoint(param: Point) {
param.draw()
}
let point = Point(x: 0, y: 0)
drawAPoint(point)
메소드 인라이닝
인라이닝 (1)
52. struct Point {
var x, y: CGFloat
func draw() {
// Point.draw implementation
}
}
func drawAPoint(param: Point) {
param.draw()
}
let point = Point(x: 0, y: 0)
point.draw()
메소드 인라이닝
인라이닝 (1)
53. struct Point {
var x, y: CGFloat
func draw() {
// Point.draw implementation
}
}
func drawAPoint(param: Point) {
param.draw()
}
let point = Point(x: 0, y: 0)
point.draw()
메소드 인라이닝
인라이닝 (2)
54. struct Point {
var x, y: CGFloat
func draw() {
// Point.draw implementation
}
}
func drawAPoint(param: Point) {
param.draw()
}
let point = Point(x: 0, y: 0)
// Point.draw implementation
메소드 인라이닝
인라이닝 (2)
55. struct Point {
var x, y: CGFloat
func draw() {
// Point.draw implementation
}
}
func drawAPoint(param: Point) {
param.draw()
}
let point = Point(x: 0, y: 0)
// Point.draw implementation
메소드 인라이닝
인라이닝 (2)
2단계의 호출이 줄었다
두 코드가 붙어 추가적인 최적화의 기회도 생겼다
56. class Drawable { func draw() {} }
class Point : Drawable {
var x, y: CGFloat
override func draw() { ... }
}
class Line : Drawable {
var x1, y1, x2, y2: CGFloat
override func draw() { ... }
}
func draw(d: Drawable, withColor color: UIColor) {
color.setFill()
d.draw()
}
Method Dispatch (Dynamic)
Reference semantics에서의 다형성 (Polymorphism)
57. class Drawable { func draw() {} }
class Point : Drawable {
var x, y: CGFloat
override func draw() { ... }
}
class Line : Drawable {
var x1, y1, x2, y2: CGFloat
override func draw() { ... }
}
func draw(d: Drawable, withColor color: UIColor) {
color.setFill()
d.draw()
}
d: Drawable
d.draw()
Method Dispatch (Dynamic)
Reference semantics에서의 다형성 (Polymorphism)
Drawable.draw? Point.draw? Line.draw?
어떻게 알지?
58. Method Dispatch (Dynamic)
Reference semantics에서의 다형성 (Polymorphism)
class Drawable { func draw() {} }
class Point : Drawable {
var x, y: CGFloat
override func draw() { ... }
}
class Line : Drawable {
var x1, y1, x2, y2: CGFloat
override func draw() { ... }
}
func draw(d: Drawable, withColor color: UIColor) {
color.setFill()
d.draw()
}
Line : Drawable
d: Drawable
d.draw()
class의 실제 type을 얻고
Line.Type
59. Method Dispatch (Dynamic)
Reference semantics에서의 다형성 (Polymorphism)
class Drawable { func draw() {} }
class Point : Drawable {
var x, y: CGFloat
override func draw() { ... }
}
class Line : Drawable {
var x1, y1, x2, y2: CGFloat
override func draw() { ... }
}
func draw(d: Drawable, withColor color: UIColor) {
color.setFill()
d.draw()
}
Line : Drawable
d: Drawable
d.draw()
그 class type에 속한 V-Table을 찾아서
Line.Type
V-Table
draw:
…
60. Method Dispatch (Dynamic)
Reference semantics에서의 다형성 (Polymorphism)
class Drawable { func draw() {} }
class Point : Drawable {
var x, y: CGFloat
override func draw() { ... }
}
class Line : Drawable {
var x1, y1, x2, y2: CGFloat
override func draw() { ... }
}
func draw(d: Drawable, withColor color: UIColor) {
color.setFill()
d.draw()
}
Line : Drawable
override func draw() { ... }
d: Drawable
d.draw()
Line.Type
V-Table
draw:
…
실제 draw의 코드 주소를 알아내어
61. Method Dispatch (Dynamic)
Reference semantics에서의 다형성 (Polymorphism)
class Drawable { func draw() {} }
class Point : Drawable {
var x, y: CGFloat
override func draw() { ... }
}
class Line : Drawable {
var x1, y1, x2, y2: CGFloat
override func draw() { ... }
}
func draw(d: Drawable, withColor color: UIColor) {
color.setFill()
d.draw()
}
Line : Drawable
override func draw() { ... }
d: Drawable
d.draw()
Line.Type
V-Table
draw:
…
호출한다
62. Dynamic Method Dispatch의 문제
요점은, 실제 Type을 컴파일 시점에 알 수가 없다는 것
때문에, 코드 주소를 runtime에 찾아야 한다
Static에 비해 단지 이것이 문제. Thread saftety문제도 없다
하지만 이로 인해 컴파일러가 최적화를 못하는 것이 큰 문제
63. Objective-C
Objective-C의 method dispatch는 Message sending 방식
[anObject doMethod:aParameter];
아래처럼 동적으로 메소드를 Lookup하여 호출된다.
objc_msgSend(anObject, @selector(doMethod:), aParameter);
강력하고 유연한 특징을 가지고 있지만 성능 저하 요소
특히 Loop안에서 빈번하게 Method 호출이 일어나는 경우
64. Static Dispatch로 강제하기
final, private 등을 쓰는 버릇
• 해당 메소드, 프로퍼티등은 상속 안 되므로 static하게 처리
dynamic 키워드 최소화
Objc 연동 최소화
• Objective-C Runtime을 통하게 됨
WMO (whole module optimization)
67. Whole Module Optimization
빌드시에 모든 파일을 한번에 분석하여,
static dispatch로 변환 가능한지 등을 판단하여 최적화
겁나 느려짐 주의 (Xcode7)
디버그 빌드에 적용하는 것은 정신 건강에 좋지 않습니다
아직 안정화가 안 됨 주의 (Xcode7) 너무 믿진 마세요…
68. 정리: 성능에 영향을 미치는 3가지
Memory Allocation: Stack or Heap
Reference Counting: No or Yes
Method Dispatch: Static or Dynamic
71. class
class Point {
var x: CGFloat
var y: CGFloat
}
let c1 = Point(x: 0.0, y: 0.0)
let c2 = c1
…
72. class
class Point {
var x: CGFloat
var y: CGFloat
}
let c1 = Point(x: 0.0, y: 0.0)
let c2 = c1
retain(c2)
…
release(c1)
release(c2)
Stack
c1:
c2:
Heap
…
refCount: 2
x: 0.0
y: 0.0
Heap, Reference Counting 사용
73. class
Memory Allocation: Heap
Reference Counting: Yes
Method Dispatch: Dynamic (V-Table)
• 성능 상관 없이 레퍼런스 시맨틱스가 필요하다면 써야함
• Identity, 상속, …
• 단 레퍼런스의 의도하치 공유로 인한 문제 조심
75. 참조 타입이 없는 struct
struct Point {
var x: CGFloat
var y: CGFloat
}
let c1 = Point(x: 0.0, y: 0.0)
let c2 = c1
76. 참조 타입이 없는 struct
struct Point {
var x: CGFloat
var y: CGFloat
}
let c1 = Point(x: 0.0, y: 0.0)
let c2 = c1
Stack
c1: x: 0.0
y: 0.0
77. 참조 타입이 없는 struct
struct Point {
var x: CGFloat
var y: CGFloat
}
let c1 = Point(x: 0.0, y: 0.0)
let c2 = c1
Stack
c1: x: 0.0
y: 0.0
c2: x: 0.0
y: 0.0
78. 참조 타입이 없는 struct
Memory Allocation: Stack
Reference Counting: No
Method Dispatch: Static
79. 참조 타입을 가진 struct
struct Label {
var text: String
var font: UIFont
}
let c1 = Label(text: “msg”,
font: font)
let c2 = c1
…
80. 참조 타입을 가진 struct
struct Label {
var text: String
var font: UIFont
}
let c1 = Label(text: “msg”,
font: font)
let c2 = c1
…
class type
81. 참조 타입을 가진 struct
struct Label {
var text: String
var font: UIFont
}
let c1 = Label(text: “msg”,
font: font)
let c2 = c1
…
class type
value type …?
82. 참조 타입을 가진 struct
struct Label {
var text: String
var font: UIFont
}
let c1 = Label(text: “msg”,
font: font)
let c2 = c1
…
class type
value type 안에 class 있음
String은 Value semantics이지만,
내부 storage로 class 타입을 가지고 있음
• Copy시 해당 프로퍼티에 reference counting이 동작한다
• (Array, Dictionary 등도 마찬가지)
83. 참조 타입을 가진 struct
struct Label {
var text: String
var font: UIFont
}
let c1 = Label(text: “msg”,
font: font)
let c2 = c1
…
84. 참조 타입을 가진 struct
struct Label {
var text: String
var font: UIFont
}
let c1 = Label(text: “msg”,
font: font)
let c2 = c1
…
Stack
c1: text:
…
_storage
font:
Heap
…
refCount: 1
…
…
refCount: 1
…
85. 참조 타입을 가진 struct
struct Label {
var text: String
var font: UIFont
}
let c1 = Label(text: “msg”,
font: font)
let c2 = c1
retain(c2.text._storage)
retain(c2.font)
…
Stack
c1: text:
…
_storage
font:
c2:
text:
…
_storage
font:
Heap
…
refCount: 2
…
…
refCount: 2
…
89. 참조 타입이 많은 struct
Memory Allocation: Stack
Reference Counting: MANY!
Method Dispatch: Static
struct HTTPRequest {
var protocol: String
var domain: String
var path: String
var filename: String
var extension: String
var query: [String: String]
var httpMethod: String
var httpVersion: String
}
90. struct내 참조 타입을 줄여보자
struct HTTPRequest {
var protocol: String // (1)
var domain: String // (2)
var path: String // (3)
var filename: String // (4)
var extension: String // (5)
var query: [String: String] // (6)
var httpMethod: String // (7)
var httpVersion: String // (8)
var httpHost: String // (9)
}
9개의 참조 타입
-> Copy할 때마다 9번의 Reference Counting
91. struct내 참조 타입을 줄여보자
struct HTTPRequest {
var protocol: String // (1)
var domain: String // (2)
var path: String // (3)
var filename: String // (4)
var extension: String // (5)
var query: [String: String] // (6)
var httpMethod: String // (7)
var httpVersion: String // (8)
var httpHost: String // (9)
}
9개의 참조 타입
-> Copy할 때마다 9번의 Reference Counting
enum HTTPMethod {
case Get, Post, Put, Delete
}
enum HTTPVersion {
case _1_0, _1_1
}
struct HTTPRequest {
var url: NSURL // (1)
var httpMethod: HTTPMethod
var httpVersion: HTTPVersion
var httpHost: String // (2)
}
2개로 줄임!
값의 제한이 가능하면 enum 등의 Value type으로 변경하기
다수의 class들을 하나의 class로 몰아 넣기
92. Protocol Type
코드 없이 API만 정의함
상속 없는 다형성 (Polymorphism) 구현이 가능
Objective C의 protocol, Java의 Interface 매우 유사함
Value type인 struct에도 적용이 가능하다
• Value semantics에서의 다형성
93. Protocol을 이용한 Value Type 다형성
protocol Drawable { func draw() }
struct Point : Drawable {
var x, y: CGFloat
func draw() { ... }
}
struct Line : Drawable {
var x1, y1, x2, y2: CGFloat
func draw() { ... }
}
var drawables: [Drawable]
…
for d in drawables {
d.draw()
}
추상 메소드 정의
추상 메소드 정의메소드 구현
변수를 Protocol type으로
실제 메소드 호출
94. 의문점: 변수 할당
class라면 주소값이니 모두 같은 사이즈지만,
struct인 Point와 Line은 사이즈가 다르다.
어떻게 Drawable에 메모리를 미리 할당해 놓고 값을 저장할까?
struct Point : Drawable {
var x, y: CGFloat
…
}
struct Line : Drawable {
var x1, y1, x2, y2: CGFloat
…
}
95. 의문점: Method Dispatch
class의 다형성 구조에선 V-Table을 통해서 찾았다.
상속이 아닌 Protocol의 다형성 구조에선 V-Table이 없다
어떻게 Point.draw와 Line.draw를 구분해서 호출할까?
var drawables: [Drawable]
…
for d in drawables {
d.draw()
}
96. Protocol type의 변수 할당
protocol Drawable { func draw() }
struct Point : Drawable {
var x, y: CGFloat
func draw() { ... }
}
struct Line : Drawable {
var x1, y1, x2, y2: CGFloat
func draw() { ... }
}
var drawables: [Drawable]
…
for d in drawables {
d.draw()
}
[Drawable]
_storage
…
Heap
refCount ? ? ?
97. [Drawable]
_storage
…
Protocol type의 변수 할당
protocol Drawable { func draw() }
struct Point : Drawable {
var x, y: CGFloat
func draw() { ... }
}
struct Line : Drawable {
var x1, y1, x2, y2: CGFloat
func draw() { ... }
}
var drawables: [Drawable]
…
for d in drawables {
d.draw()
}
Point
x: 0.0
y: 0.0
Line
x1: 0.0
y1: 0.0
x2: 1.0
y2: 1.0
refCount ? ? ?
98. Protocol type의 변수 할당
protocol Drawable { func draw() }
struct Point : Drawable {
var x, y: CGFloat
func draw() { ... }
}
struct Line : Drawable {
var x1, y1, x2, y2: CGFloat
func draw() { ... }
}
var drawables: [Drawable]
…
for d in drawables {
d.draw()
}
모두 같은 사이즈
다른 사이즈
어떻게 넣을까?
refCount ? ? ?
[Drawable]
_storage
…
Point
x: 0.0
y: 0.0
Line
x1: 0.0
y1: 0.0
x2: 1.0
y2: 1.0
103. Value Witness Table (VWT)
VWT
allocate:
copy:
destruct:
deallocate:
Existential container의 생성/해제를 담당하는 인터페이스
104. Value Witness Table (VWT)
Line VWT
allocate:
copy:
destruct:
deallocate:
Point VWT
allocate:
copy:
destruct:
deallocate:
Protocol을 구현하는 type마다 있다
105. Drawable
Drawable
Value Witness Table (VWT)
Line VWT
allocate:
copy:
destruct:
deallocate:
Point VWT
allocate:
copy:
destruct:
deallocate:
실제 변수 영역
Existential Container
114. Copy 동작 정리
Value 타입이므로 값 전체가 Copy된다.
3 words 이하의 경우
• 단순히 새로운 Existential container에 전체가 복사됨
3 words를 넘는 경우
• 새로운 Existential container 생성
• 값 전체가 새로운 Heap할당 후 복사됨
115. 큰 사이즈 protocol 타입의 copy
protocol Drawable { func draw() }
struct Line : Drawable {
var x1, y1, x2, y2: CGFloat
…
}
var line: Drawable = Line()
var copy: Drawable = line
copy.x2 = 1.0
116. Drawable
line: ref:
vwt:
pwt:
큰 사이즈 protocol 타입의 copy
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
Heapprotocol Drawable { func draw() }
struct Line : Drawable {
var x1, y1, x2, y2: CGFloat
…
}
var line: Drawable = Line()
var copy: Drawable = line
copy.x2 = 1.0
117. Drawable
line: ref:
vwt:
pwt:
큰 사이즈 protocol 타입의 copy
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
Heapprotocol Drawable { func draw() }
struct Line : Drawable {
var x1, y1, x2, y2: CGFloat
…
}
var line: Drawable = Line()
var copy: Drawable = line
copy.x2 = 1.0
Drawable
copy: ref:
vwt:
pwt:
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
copy
118. Drawable
line: ref:
vwt:
pwt:
큰 사이즈 protocol 타입의 copy
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
Heapprotocol Drawable { func draw() }
struct Line : Drawable {
var x1, y1, x2, y2: CGFloat
…
}
var line: Drawable = Line()
var copy: Drawable = line
copy.x2 = 1.0
Drawable
copy: ref:
vwt:
pwt:
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
copy
Heap의 데이터도 복사가된다!
119. Drawable
line: ref:
vwt:
pwt:
큰 사이즈 protocol 타입의 copy
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
Heapprotocol Drawable { func draw() }
struct Line : Drawable {
var x1, y1, x2, y2: CGFloat
…
}
var line: Drawable = Line()
var copy: Drawable = line
copy.x2 = 1.0
Drawable
copy: ref:
vwt:
pwt:
x1: 0.0
y1: 0.0
x2: 1.0
y2: 0.0
copy
Heap의 데이터도 복사가된다!
나름 Value type이니까!
Heap은 쓰지만 Reference counting이 없다
120. Drawable
line: ref:
vwt:
pwt:
큰 사이즈 protocol 타입의 copy
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
Heapprotocol Drawable { func draw() }
struct Line : Drawable {
var x1, y1, x2, y2: CGFloat
…
}
var line: Drawable = Line()
var copy: Drawable = line
copy.x2 = 1.0
Drawable
copy: ref:
vwt:
pwt:
copy
Copy마다 새로운 Heap 할당하는데 이것이 큰 성능 저하 요소!
x1: 0.0
y1: 0.0
x2: 1.0
y2: 0.0
121. 개선해 봅시다
protocol Drawable { func draw() }
struct Line : Drawable {
var x1, y1, x2, y2: CGFloat
…
}
var line: Drawable = Line()
var copy: Drawable = line
//copy.x2 = 1.0
122. Indirect Storage
protocol Drawable { func draw() }
class LineStorage {
var x1, y1, x2, y2: CGFloat
…
}
struct Line : Drawable {
private var _storage: LineStorage
…
}
var line: Drawable = Line()
var copy: Drawable = line
//copy.x2 = 1.0
class 타입의 간접 저장소로 이동
123. Indirect Storage
protocol Drawable { func draw() }
class LineStorage {
var x1, y1, x2, y2: CGFloat
…
}
struct Line : Drawable {
private var _storage: LineStorage
…
}
var line: Drawable = Line()
var copy: Drawable = line
//copy.x2 = 1.0
Drawable
line: _storage:
vwt:
pwt:
Heap
…
refCount: 1
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
124. Indirect Storage
protocol Drawable { func draw() }
class LineStorage {
var x1, y1, x2, y2: CGFloat
…
}
struct Line : Drawable {
private var _storage: LineStorage
…
}
var line: Drawable = Line()
var copy: Drawable = line
//copy.x2 = 1.0
Drawable
line: _storage:
vwt:
pwt:
Heap
Drawable
copy: _storage:
vwt:
pwt:
…
refCount: 2
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
125. Indirect Storage
protocol Drawable { func draw() }
class LineStorage {
var x1, y1, x2, y2: CGFloat
…
}
struct Line : Drawable {
private var _storage: LineStorage
…
}
var line: Drawable = Line()
var copy: Drawable = line
//copy.x2 = 1.0
Heap
Heap할당이 더 싼 Reference counting으로 바뀌었다
…
refCount: 2
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
Drawable
line: _storage:
vwt:
pwt:
Drawable
copy: _storage:
vwt:
pwt:
126. Indirect Storage
protocol Drawable { func draw() }
class LineStorage {
var x1, y1, x2, y2: CGFloat
…
}
struct Line : Drawable {
private var _storage: LineStorage
…
}
var line: Drawable = Line()
var copy: Drawable = line
copy.x2 = 1.0
Heap
…
refCount: 2
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
하지만 값을 바꾼다면?
Drawable
line: _storage:
vwt:
pwt:
Drawable
copy: _storage:
vwt:
pwt:
127. Indirect Storage
protocol Drawable { func draw() }
class LineStorage {
var x1, y1, x2, y2: CGFloat
…
}
struct Line : Drawable {
private var _storage: LineStorage
…
}
var line: Drawable = Line()
var copy: Drawable = line
copy.x2 = 1.0
Heap
…
refCount: 2
x1: 0.0
y1: 0.0
x2: 1.0
y2: 0.0
둘 다 바뀌어 버림!
Drawable
line: _storage:
vwt:
pwt:
Drawable
copy: _storage:
vwt:
pwt:
128. Copy-on-Write
protocol Drawable { func draw() }
class LineStorage {
var x1, y1, x2, y2: CGFloat
…
}
struct Line : Drawable {
private var _storage: LineStorage
…
var x2: CGFloat {
get { return _storage.x2 }
set {
if !isUniquelyReferencedNonObjC(&_storage) {
_storage = LineStorage(_storage)
}
_storage.x2 = x2
}
}
…
}
var line: Drawable = Line()
var copy: Drawable = line
copy.x2 = 1.0
Heap
…
refCount: 2
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
Drawable
line: _storage:
vwt:
pwt:
Drawable
copy: _storage:
vwt:
pwt:
129. Copy-on-Write
protocol Drawable { func draw() }
class LineStorage {
var x1, y1, x2, y2: CGFloat
…
}
struct Line : Drawable {
private var _storage: LineStorage
…
var x2: CGFloat {
get { return _storage.x2 }
set {
if !isUniquelyReferencedNonObjC(&_storage) {
_storage = LineStorage(_storage)
}
_storage.x2 = x2
}
}
…
}
var line: Drawable = Line()
var copy: Drawable = line
copy.x2 = 1.0
Heap
…
refCount: 1
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
…
refCount: 1
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
Drawable
line: _storage:
vwt:
pwt:
Drawable
copy: _storage:
vwt:
pwt:
130. Copy-on-Write
protocol Drawable { func draw() }
class LineStorage {
var x1, y1, x2, y2: CGFloat
…
}
struct Line : Drawable {
private var _storage: LineStorage
…
var x2: CGFloat {
get { return _storage.x2 }
set {
if !isUniquelyReferencedNonObjC(&_storage) {
_storage = LineStorage(_storage)
}
_storage.x2 = x2
}
}
…
}
var line: Drawable = Line()
var copy: Drawable = line
copy.x2 = 1.0
Heap
…
refCount: 1
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
…
refCount: 1
x1: 0.0
y1: 0.0
x2: 1.0
y2: 0.0
Drawable
line: _storage:
vwt:
pwt:
Drawable
copy: _storage:
vwt:
pwt:
131. Existential Container
변수가 Protocol type으로 정의된 경우 쓰임
프로토콜을 통한 다형성을 구현하기 위한 목적으로 쓰임
내부 동작이 복잡하긴해도 성능이 class 쓰는것과 비슷하다
• 둘 다 초기화 시 Heap 할당하여 사용
• 둘 다 Dynamic dispatch (class도 V-Table, protocol은
PWT)
132. 큰 사이즈 protocol 타입의 copy
Indirect Storage
• Copy시 Heap 할당 대신 Reference counting으로 대체
• class타입의 다형성 쓸때와 비슷한 수준
Copy-on-Write
• Indirect storage를 값이 변경될 시점에 Heap 할당하여 복사
• 성능 저하를 최소화 함 (변경 동작에서만)
String, Array, Dictionary 등도 이런 개념으로 Value
semantics 구현
133. 작은 사이즈의 Protocol Type
Memory Allocation: Stack
Reference Counting: No
Method Dispatch: Dynamic (Protocol Witness Table)
134. 큰 사이즈의 Protocol Type
Memory Allocation: MANY! (Copy할 때마다 할당)
Reference Counting: No (class 프로퍼티가 있을 때만)
Method Dispatch: Dynamic (Protocol Witness Table)
135. 큰 사이즈의 Protocol Type
Memory Allocation: Heap
Reference Counting: Yes
Method Dispatch: Dynamic (Protocol Witness Table)
with Indirect Storage
140. Generics Type
protocol Drawable { func draw() }
func drawACopy<T: Drawable>(local: T) {
local.draw()
}
drawACopy(Point(…))
drawACopy(Line(…))
Drawable
local: x: 0.0
y: 0.0
vwt:
pwt:
Point Drawable
draw:
…
Dynamic Method Dispatch
성능 개선할 수 있을까?
141. Generics Type
protocol Drawable { func draw() }
func drawACopy<T: Drawable>(local: T) {
local.draw()
}
drawACopy(Point(…))
drawACopy(Line(…))
정적 다형성
(Static Polymorphism)
Method 내에서는 Drawable의
실제 타입이 바뀌지 않는다
142. Generics Type
protocol Drawable { func draw() }
func drawACopyForPoint(local: Point) {
d.draw()
}
func drawACopyForLine(local: Line) {
d.draw()
}
drawACopyForPoint(Point(…))
drawACopyForLine(Line(…))
복잡한 Existential Container 안 써도 됨
함수 호출 시 Heap 할당을 아주 없앨 수 있음
실제 타입별로 만들어 준다면
(Generics 특수화)
143. Generics Type
protocol Drawable { func draw() }
func drawACopyForPoint(local: Point) {
d.draw()
}
func drawACopyForLine(local: Line) {
d.draw()
}
drawACopyForPoint(Point(…))
drawACopyForLine(Line(…))
Static Method Dispatch 가 되어
컴파일러 최적화가 가능하게 되었다 (인라이닝 등)
실제 타입별로 만들어 준다면
(Generics 특수화)
152. 스위프트의 성능
Objective-C에 비해 큰 향상이 있었으나
Value 타입과 Protocol 타입 등의 성격을 고려해야 함
성능 최적화를 고려해야하는 경우의 예
• 렌더링 관련 로직 등 반복적으로 매우 빈번히 불리는 경우
• 서버 환경에서의 대용량 데이터 처리
153. 추상화 기법의 선택
Struct: 엔티티 등 Value 시맨틱이 맞는 부분
Class: Identity가 필요한 부분, 상속등의 OOP, Objective-C
Generics: 정적 다형성으로 가능한 경우
Protocol: 동적 다형성이 필요한 경우
154. 고려할 수 있는 성능 최적화 기법들
Struct에 클래스 타입의 Property가 많으면
• enum, struct등 Value type으로 대체
• Reference counting 줄임
Protocol Type을 쓸 때 대상이 큰 struct면
• Indirect storage로 struct 구조 변경
• Mutable해야하면 Copy-on-Write 구현
155. 고려할 수 있는 성능 최적화 기법들
Dynamic method dispatch를 static하게
• final, private의 생활화
• dynamic 사용 최소화
• Objc 연동 최소화 하기
• 릴리즈 빌드에 WMO 옵션 적용 고려
156. 마지막으로
정답은 없습니다.
잘 된 디자인을 해치면서까지,
모든 경우에 반드시 적용 해야하는 것은 아닙니다.
돌아가는 환경, 데이터의 특성과 다루는 양 등에 따라 다릅니다.
하지만 배경을 알면 옳은 방향으로 향할 수가 있습니다.
157. 참고
WWDC 2016
• Session 416: Understanding Swift Performance
WWDC 2015
• Session 409: Optimizing Swift Performance
• Session 414: Building Better Apps with Value Types in Swift