Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

스위프트 성능 이해하기

13,087 views

Published on

LetSwift 2016 컨퍼런스 발표 자료
Value 타입, Protocol과 스위프트의 성능 최적화

'WWDC 2016, 416: Understanding Swift Performance'의 해설

Published in: Software
  • Be the first to comment

스위프트 성능 이해하기

  1. 1. let swift( 스위프트 성능 이해하기 Value 타입, Protocol과 스위프트의 성능 최적화 @inkyfox유용하
  2. 2. 내용 Value Semantics 성능을 위해 고려할 것들 스위프트의 추상화 기법들과 성능
  3. 3. let swift( Value Semantics
  4. 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. 5. Value Type의 특징 변수 할당 시 Stack에 값 전체가 저장됨 다른 변수에 할당될 때 전체 값이 복사됨 (copy by value) • 변수들이 분리됨: 하나를 변경해도 다른 것에 영향 없음 Heap을 안 쓰며 따라서 Reference Counting도 필요 없음
  6. 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. 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. 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. 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. 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. 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. 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. 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. 14. Value Semantics: ’값’에 의해 구분됨 Value semantics에서는 Identity가 아니라 Value가 중요하다. 각 변수는 값(Value)에 의해 구분이 되어야한다. 따라서 동치 관계여야 한다. -> 간단합니다, Equatable을 구현하세요 (단순히 데이터를 전달할 목적인 struct 변수를 말하는 것이 아님)
  15. 15. Equatable 간단 구현 protocol Equatable { func ==(lhs: Self, rhs: Self) -> Bool }
  16. 16. Equatable 간단 구현 protocol Equatable { func ==(lhs: Self, rhs: Self) -> Bool } extension CGPoint: Equatable { } func ==(lhs: CGPoint, rhs: CGPoint) -> Bool { return lhs.x == rhs.x && lhs.y && rhs.y }
  17. 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간 의도하지 않은 공유로부터 안전함!
  18. 18. 그래도.. 값 모두를 Copy하는데 성능 괜찮을까?
  19. 19. Copy는 빠르다 기본 타입들, enum, tuple, struct • 정해진 시간 (constant time) 안에 끝남 내부 데이터가 Heap과 혼용하는 struct의 경우 • 정해진 시간 + 레퍼런스 copy등의 시간 • String, Array, Set, Dictionary 등 • 쓰기 시 Copy-on-write로 속도 저하 보완
  20. 20. Immutable로 해도 되는것 아닌가?
  21. 21. Immutable 참조형이어도 값이 불변하면 Thread간에 문제생길 일이 없음 함수형 패러다임과 같이 널리 전파됨 Immutable은 cocoa에서도 꽤 써 왔다 • NSArray *array = [array arrayByAddingObject: component]; • NSURL *url = [url URLByAppendingPathComponent: component];
  22. 22. 정말 Immutable이 언제나 답일까?
  23. 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. 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. 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. 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. 27. API가 이상해지는 경우도 // 속도가 변경되었다 // Mutable + Value Type car.dashboard.speed = 99 // Immutable + Reference Type car.dashboard = Dashboard(speed: 99, rpm: car.dashboard.rpm) 그것도 그렇지만, Dashboard를 바꾼 다는 의미인건가?
  28. 28. 그래도 class도 중요한 경우 Value보단 Identity가 중요한 경우 • UIView 같이 모든 변수에서 단 하나의 state를 갖는 개체 OOP 모델 • 여전히 상속은 아주 훌륭한 도구 Objective-C 연동 Indirect storage (특수한 경우 struct내의 간접 저장소 역할) • 뒤에서 설명
  29. 29. let swift( 성능을 위해 고려할 것들
  30. 30. 성능에 영향을 미치는 3가지 Memory Allocation: Stack or Heap Reference Counting: No or Yes Method dispatch: Static or Dynamic
  31. 31. Heap 할당의 문제 할당시에 빈 곳을 찾고 관리하는 것은 복잡한 과정 Heap
  32. 32. Heap 할당의 문제 할당시에 빈 곳을 찾고 관리하는 것은 복잡한 과정 무엇보다 그 과정이 thread safe해야한다는 점이 가장 큰 문제 • lock 등의 synchronization 동작은 큰 성능 저하 요소 반면 Stack 할당은 • 단순히 스택포인터 변수값만 바꿔주는 정도
  33. 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. 34. Heap 할당 줄이기 struct Attribute { var color: Color var theme: Theme var selected: Bool } 새로운 Key 타입 정의
  35. 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. 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. 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. 38. Reference Counting의 문제 정말 자주 실행된다 • 변수 Copy할 때 마다 그러나 이것도 역시 가장 큰 문제는 thread safety 때문 • 카운트를 Atomic하게 늘리고 줄여야함
  39. 39. class MyClass { } func foo(c: MyClass) { … } do { let c0: MyClass = MyClass() var c1: MyClass? = c0 foo(c0) c1 = nil } Reference Counting의 동작
  40. 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. 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. 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. 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. 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. 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. 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. 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. 48. Method Dispatch (Static) 컴파일 시점에 메소드의 실제 코드 위치를 안다면 실행중 찾는 과정 없이 바로 해당 코드 주소로 점프할 수 있음 컴파일러의 최적화, 메소드 인라이닝 (Inlining) 가능
  49. 49. 메소드 인라이닝 컴파일 시점에 메소드 호출 부분에 메소드 내용을 붙여넣음 • 효과가 있다고 판단되는 경우에만 Call stack 오버헤드 줄임 • CPU icache나 레지스터를 효율적으로 쓸 가능성 컴파일러의 추가 최적화 가능 • 최근 메소드들이 작으므로 더더욱 기회가 많음 • 루프 안에서 불리는 경우 큰 효과
  50. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 62. Dynamic Method Dispatch의 문제 요점은, 실제 Type을 컴파일 시점에 알 수가 없다는 것 때문에, 코드 주소를 runtime에 찾아야 한다 Static에 비해 단지 이것이 문제. Thread saftety문제도 없다 하지만 이로 인해 컴파일러가 최적화를 못하는 것이 큰 문제
  63. 63. Objective-C Objective-C의 method dispatch는 Message sending 방식 [anObject doMethod:aParameter]; 아래처럼 동적으로 메소드를 Lookup하여 호출된다. objc_msgSend(anObject, @selector(doMethod:), aParameter); 강력하고 유연한 특징을 가지고 있지만 성능 저하 요소 특히 Loop안에서 빈번하게 Method 호출이 일어나는 경우
  64. 64. Static Dispatch로 강제하기 final, private 등을 쓰는 버릇 • 해당 메소드, 프로퍼티등은 상속 안 되므로 static하게 처리 dynamic 키워드 최소화 Objc 연동 최소화 • Objective-C Runtime을 통하게 됨 WMO (whole module optimization)
  65. 65. Whole Module Optimization 빌드시에 모든 파일을 한번에 분석하여, static dispatch로 변환 가능한지 등을 판단하여 최적화
  66. 66. Whole Module Optimization 빌드시에 모든 파일을 한번에 분석하여, static dispatch로 변환 가능한지 등을 판단하여 최적화
  67. 67. Whole Module Optimization 빌드시에 모든 파일을 한번에 분석하여, static dispatch로 변환 가능한지 등을 판단하여 최적화 겁나 느려짐 주의 (Xcode7) 디버그 빌드에 적용하는 것은 정신 건강에 좋지 않습니다 아직 안정화가 안 됨 주의 (Xcode7) 너무 믿진 마세요…
  68. 68. 정리: 성능에 영향을 미치는 3가지 Memory Allocation: Stack or Heap Reference Counting: No or Yes Method Dispatch: Static or Dynamic
  69. 69. let swift( 스위프트의 추상화 기법들과 성능
  70. 70. 추상화 기법들 Class Struct Protocol Type Generics Type 각각 앞서 소개한 성능 요소들에 대해 어떤 특징을 가지는가
  71. 71. class class Point { var x: CGFloat var y: CGFloat } let c1 = Point(x: 0.0, y: 0.0) let c2 = c1 …
  72. 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. 73. class Memory Allocation: Heap Reference Counting: Yes Method Dispatch: Dynamic (V-Table) • 성능 상관 없이 레퍼런스 시맨틱스가 필요하다면 써야함 • Identity, 상속, … • 단 레퍼런스의 의도하치 공유로 인한 문제 조심
  74. 74. final class Memory Allocation: Heap Reference Counting: Yes Method Dispatch: Static
  75. 75. 참조 타입이 없는 struct struct Point { var x: CGFloat var y: CGFloat } let c1 = Point(x: 0.0, y: 0.0) let c2 = c1
  76. 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. 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. 78. 참조 타입이 없는 struct Memory Allocation: Stack Reference Counting: No Method Dispatch: Static
  79. 79. 참조 타입을 가진 struct struct Label { var text: String var font: UIFont } let c1 = Label(text: “msg”, font: font) let c2 = c1 …
  80. 80. 참조 타입을 가진 struct struct Label { var text: String var font: UIFont } let c1 = Label(text: “msg”, font: font) let c2 = c1 … class type
  81. 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. 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. 83. 참조 타입을 가진 struct struct Label { var text: String var font: UIFont } let c1 = Label(text: “msg”, font: font) let c2 = c1 …
  84. 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. 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 …
  86. 86. 참조 타입을 가진 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) … release(c1.text._storage) release(c1) release(c2.text._storage) release(c2) Stack c1: text: … _storage font: c2: text: … _storage font: Heap … refCount: 0 … … refCount: 0 …
  87. 87. 참조 타입을 가진 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) … release(c1.text._storage) release(c1) release(c2.text._storage) release(c2) Reference Counting이 한번 Copy할때마다 2번씩 일어난다!! struct안에 참조 타입의 property 수만큼 많아진다.
  88. 88. 참조 타입을 가진 struct Memory Allocation: Stack Reference Counting: Yes Method Dispatch: Static
  89. 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. 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. 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. 92. Protocol Type 코드 없이 API만 정의함 상속 없는 다형성 (Polymorphism) 구현이 가능 Objective C의 protocol, Java의 Interface 매우 유사함 Value type인 struct에도 적용이 가능하다 • Value semantics에서의 다형성
  93. 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. 94. 의문점: 변수 할당 class라면 주소값이니 모두 같은 사이즈지만, struct인 Point와 Line은 사이즈가 다르다. 어떻게 Drawable에 메모리를 미리 할당해 놓고 값을 저장할까? struct Point : Drawable { var x, y: CGFloat … } struct Line : Drawable { var x1, y1, x2, y2: CGFloat … }
  95. 95. 의문점: Method Dispatch class의 다형성 구조에선 V-Table을 통해서 찾았다. 상속이 아닌 Protocol의 다형성 구조에선 V-Table이 없다 어떻게 Point.draw와 Line.draw를 구분해서 호출할까? var drawables: [Drawable] … for d in drawables { d.draw() }
  96. 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. 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. 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
  99. 99. Existential Container Value Buffer (3 words) Protocol type의 실제 값을 넣고 관리하는 구조 (1 word는 32bit CPU에서는 32bit, 64bit CPU에서는 64bit) (Fixed size)
  100. 100. Existential Container Drawable Existencex: 0.0 y: 0.0 struct가 3 words 이하인 경우 Point x: 0.0 y: 0.0 Existential container 안에 값 모두 저장됨 Existential Container
  101. 101. Drawable ref Line x1: 0.0 y1: 0.0 x2: 1.0 y2: 1.0 Existential Container struct가 3 words보다 큰 경우 x1: 0.0 y1: 0.0 x2: 1.0 y2: 1.0 Heap Existential Container Heap 할당하여 값 저장 Existential container에 해당 레퍼런스 저장
  102. 102. 어떻게 3 word를 구분해 할당하고 복사하는가?
  103. 103. Value Witness Table (VWT) VWT allocate: copy: destruct: deallocate: Existential container의 생성/해제를 담당하는 인터페이스
  104. 104. Value Witness Table (VWT) Line VWT allocate: copy: destruct: deallocate: Point VWT allocate: copy: destruct: deallocate: Protocol을 구현하는 type마다 있다
  105. 105. Drawable Drawable Value Witness Table (VWT) Line VWT allocate: copy: destruct: deallocate: Point VWT allocate: copy: destruct: deallocate: 실제 변수 영역 Existential Container
  106. 106. Line VWT allocate: copy: destruct: deallocate: Point VWT allocate: copy: destruct: deallocate: Drawable Drawable ref: Value Witness Table (VWT) Heap Existential Container
  107. 107. Line VWT allocate: copy: destruct: deallocate: Point VWT allocate: copy: destruct: deallocate: Drawable x: 0.0 y: 0.0 Drawable ref: Value Witness Table (VWT) x1: 0.0 y1: 0.0 x2: 1.0 y2: 1.0 Heap Existential Container
  108. 108. Line VWT allocate: copy: destruct: deallocate: Point VWT allocate: copy: destruct: deallocate: Drawable Drawable ref: Value Witness Table (VWT) Heap Existential Container
  109. 109. Line VWT allocate: copy: destruct: deallocate: Point VWT allocate: copy: destruct: deallocate: Drawable Drawable Value Witness Table (VWT) Existential Container
  110. 110. Drawable x: 0.0 y: 0.0 vwt: Drawable ref: vwt: Value Witness Table (VWT) x1: 0.0 y1: 0.0 x2: 1.0 y2: 1.0 Heap Line VWT allocate: copy: destruct: deallocate: Point VWT allocate: copy: destruct: deallocate: Existential Container
  111. 111. Drawable x: 0.0 y: 0.0 vwt: Drawable ref: vwt: Method Dispatch는? x1: 0.0 y1: 0.0 x2: 1.0 y2: 1.0 Heap Protocol Witness Table Line VWT allocate: copy: destruct: deallocate: Point VWT allocate: copy: destruct: deallocate: Point Drawable draw: … Line Drawable draw: … Existential Container
  112. 112. Point Drawable draw: … Line Drawable draw: … Drawable x: 0.0 y: 0.0 vwt: Drawable ref: vwt: Method Dispatch는? x1: 0.0 y1: 0.0 x2: 1.0 y2: 1.0 Heap Line VWT allocate: copy: destruct: deallocate: Point VWT allocate: copy: destruct: deallocate: Existential Container Protocol Witness Table
  113. 113. Drawable x: 0.0 y: 0.0 vwt: pwt: Drawable ref: vwt: pwt: Method Dispatch는? Line VWT allocate: copy: destruct: deallocate: x1: 0.0 y1: 0.0 x2: 1.0 y2: 1.0 Heap Point VWT allocate: copy: destruct: deallocate: Point Drawable draw: … Line Drawable draw: … Dynamic Method Dispatch Existential Container Protocol Witness Table
  114. 114. Copy 동작 정리 Value 타입이므로 값 전체가 Copy된다. 3 words 이하의 경우 • 단순히 새로운 Existential container에 전체가 복사됨 3 words를 넘는 경우 • 새로운 Existential container 생성 • 값 전체가 새로운 Heap할당 후 복사됨
  115. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 131. Existential Container 변수가 Protocol type으로 정의된 경우 쓰임 프로토콜을 통한 다형성을 구현하기 위한 목적으로 쓰임 내부 동작이 복잡하긴해도 성능이 class 쓰는것과 비슷하다 • 둘 다 초기화 시 Heap 할당하여 사용 • 둘 다 Dynamic dispatch (class도 V-Table, protocol은 PWT)
  132. 132. 큰 사이즈 protocol 타입의 copy Indirect Storage • Copy시 Heap 할당 대신 Reference counting으로 대체 • class타입의 다형성 쓸때와 비슷한 수준 Copy-on-Write • Indirect storage를 값이 변경될 시점에 Heap 할당하여 복사 • 성능 저하를 최소화 함 (변경 동작에서만) String, Array, Dictionary 등도 이런 개념으로 Value semantics 구현
  133. 133. 작은 사이즈의 Protocol Type Memory Allocation: Stack Reference Counting: No Method Dispatch: Dynamic (Protocol Witness Table)
  134. 134. 큰 사이즈의 Protocol Type Memory Allocation: MANY! (Copy할 때마다 할당) Reference Counting: No (class 프로퍼티가 있을 때만) Method Dispatch: Dynamic (Protocol Witness Table)
  135. 135. 큰 사이즈의 Protocol Type Memory Allocation: Heap Reference Counting: Yes Method Dispatch: Dynamic (Protocol Witness Table) with Indirect Storage
  136. 136. Generics Type protocol Drawable { func draw() } func drawACopy<T: Drawable>(local: T) { local.draw() } drawACopy(Point(…)) drawACopy(Line(…))
  137. 137. Generics Type protocol Drawable { func draw() } func drawACopy<T: Drawable>(local: T) { local.draw() } drawACopy(Point(…)) drawACopy(Line(…)) Point VWT allocate: copy: destruct: deallocate: Drawable local: x: 0.0 y: 0.0 vwt: pwt: VWT 이용하여 값 복사
  138. 138. Generics Type protocol Drawable { func draw() } func drawACopy<T: Drawable>(local: T) { local.draw() } drawACopy(Point(…)) drawACopy(Line(…)) Line VWT allocate: copy: destruct: deallocate: Drawable local: ref vwt: pwt: VWT 이용하여 메모리 할당, 값 복사 x1: 0.0 y1: 0.0 x2: 0.0 y2: 0.0
  139. 139. 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
  140. 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. 141. Generics Type protocol Drawable { func draw() } func drawACopy<T: Drawable>(local: T) { local.draw() } drawACopy(Point(…)) drawACopy(Line(…)) 정적 다형성 (Static Polymorphism) Method 내에서는 Drawable의 실제 타입이 바뀌지 않는다
  142. 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. 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 특수화)
  144. 144. 이걸 손으로 하면, Generics 쓰지 말란 말?
  145. 145. Generic 특수화 (Specialization) 컴파일러가 해 줍니다. 더 효과를 보려면 WMO (Whole Module Optimization) 이용 아직 너무 믿진 마세요. (Xcode 7)
  146. 146. Generics Type 정리 정적 다형성 (Static Polymorphism) • 컴파일 시점에 부르는 곳마다 타입이 정해져 있음 • 런타임에 바뀌지 않음 • 특수화 (Specialization)가 가능
  147. 147. 특수화 되지 않은 Generics (작은 사이즈의 Protocol Type) Memory Allocation: Stack Reference Counting: No Method Dispatch: Dynamic (Protocol Witness Table)
  148. 148. 특수화된 Generics Type (struct) Memory Allocation: Stack Reference Counting: No Method Dispatch: Static
  149. 149. 특수화 되지 않은 Generics (큰 사이즈의 Protocol Type) Memory Allocation: MANY! (Copy할 때마다 할당) Reference Counting: No (class 프로퍼티가 있을 때만) Method Dispatch: Dynamic (Protocol Witness Table)
  150. 150. 특수화된 Generics Type (class) Memory Allocation: Heap Reference Counting: Yes Method Dispatch: Dynamic (V-Table)
  151. 151. let swift( 정리
  152. 152. 스위프트의 성능 Objective-C에 비해 큰 향상이 있었으나 Value 타입과 Protocol 타입 등의 성격을 고려해야 함 성능 최적화를 고려해야하는 경우의 예 • 렌더링 관련 로직 등 반복적으로 매우 빈번히 불리는 경우 • 서버 환경에서의 대용량 데이터 처리
  153. 153. 추상화 기법의 선택 Struct: 엔티티 등 Value 시맨틱이 맞는 부분 Class: Identity가 필요한 부분, 상속등의 OOP, Objective-C Generics: 정적 다형성으로 가능한 경우 Protocol: 동적 다형성이 필요한 경우
  154. 154. 고려할 수 있는 성능 최적화 기법들 Struct에 클래스 타입의 Property가 많으면 • enum, struct등 Value type으로 대체 • Reference counting 줄임 Protocol Type을 쓸 때 대상이 큰 struct면 • Indirect storage로 struct 구조 변경 • Mutable해야하면 Copy-on-Write 구현
  155. 155. 고려할 수 있는 성능 최적화 기법들 Dynamic method dispatch를 static하게 • final, private의 생활화 • dynamic 사용 최소화 • Objc 연동 최소화 하기 • 릴리즈 빌드에 WMO 옵션 적용 고려
  156. 156. 마지막으로 정답은 없습니다. 잘 된 디자인을 해치면서까지, 모든 경우에 반드시 적용 해야하는 것은 아닙니다. 돌아가는 환경, 데이터의 특성과 다루는 양 등에 따라 다릅니다. 하지만 배경을 알면 옳은 방향으로 향할 수가 있습니다.
  157. 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
  158. 158. let swift(16)

×