스위프트 성능 이해하기
-
Upload
yong-ha-yoo -
Category
Software
-
view
8.661 -
download
6
Transcript of 스위프트 성능 이해하기
let swift(16)
스위프트�성능�이해하기Value�타입,�Protocol과�스위프트의�성능�최적화
@inkyfox유용하
내용
Value�Semantics�
성능을�위해�고려할�것들�
스위프트의�추상화�기법들과�성능�
let swift(16)
Value�Semantics
Value�Semantics
Value�Type�Semantics�/�Copy-by-Value�Semantics�
Identity가�아닌�Value(값)에만�의미를�둔다�
• Int,�Double�등의�기본�타입들�
포인터만�복사되는�참조(Reference)�시맨틱스와�비교됨�
• Objective-C,�Java�등�
스위프트엔�Objc에�없던�새로운�Value�Type을�도입�
• struct,�enum,�tuple
Value�Type의�특징
변수�할당�시�Stack에�값�전체가�저장됨�
다른�변수에�할당될�때�전체�값이�복사됨�(copy�by�value)�
• 변수들이�분리됨:�하나를�변경해도�다른�것에�영향�없음�
Heap을�안�쓰며�따라서�Reference�Counting도�필요�없음
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
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
Stackc1: ref
Heap…refCount: 1x: 0.0y: 0.0
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
Stackc1: refc2: ref
Heap…refCount: 2x: 0.0y: 0.0
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
Stackc1: refc2: ref
Heap…refCount: 2x: 0.0y: 2.0
Reference�타입은�하나의�Identity�변수가�Copy되어도�값이�하나를�향해�같은�값을�가진다
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
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
Stackc1: x: 0.0
y: 0.0
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
Stackc1: x: 0.0
y: 0.0c2: x: 0.0
y: 0.0
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
Stackc1: x: 0.0
y: 0.0c2: x: 0.0
y: 2.0
Value�타입의�각자의�변수는�Copy되어도�분리되어있다
Value�Semantics:�’값’에�의해�구분됨
Value�semantics에서는�Identity가�아니라�Value가�중요하다.�
각�변수는�값(Value)에�의해�구분이�되어야한다.�
따라서�동치�관계여야�한다.�
->�간단합니다,�Equatable을�구현하세요�
(단순히�데이터를�전달할�목적인�struct�변수를�말하는�것이�아님)
Equatable�간단�구현
protocol Equatable { func ==(lhs: Self, rhs: Self) -> Bool }
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 }
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간�의도하지�않은�공유로부터�안전함!
그래도..�값�모두를�Copy하는데��성능�괜찮을까?
Copy는�빠르다
기본�타입들,�enum,�tuple,�struct�
• 정해진�시간�(constant�time)�안에�끝남��
내부�데이터가�Heap과�혼용하는�struct의�경우�
• 정해진�시간�+�레퍼런스�copy등의�시간�
• String,�Array,�Set,�Dictionary�등�
• 쓰기�시�Copy-on-write로�속도�저하�보완�
Immutable로�해도�되는것�아닌가?
Immutable
참조형이어도�값이�불변하면�Thread간에�문제생길�일이�없음�
함수형�패러다임과�같이��널리�전파됨�
Immutable은�cocoa에서도�꽤�써�왔다�
• NSArray *array = [array arrayByAddingObject: component];
• NSURL *url = [url URLByAppendingPathComponent: component];
정말�Immutable이�언제나�답일까?
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�방식
비효율적이다
Mutable이�효율적인�경우
func makeURL(subDirectories: [String]) -> NSURL? { var array: NSMutableArray = [NSHomeDirectory()] for dir in subDirectories { array.addObject(dir) } return NSURL.fileURLWithPathComponents(array as! [String]) }
Mutable로�바꾸자
Mutable이�효율적인�경우
func makeURL(subDirectories: [String]) -> NSURL? { var array: [String] = [NSHomeDirectory()] for dir in subDirectories { array.append(dir) } return NSURL.fileURLWithPathComponents(array) }
Swift의�Array를�쓰면
API가�이상해지는�경우도
// 속도가 변경되었다
// Mutable + Value Type car.dashboard.speed = 99
// Immutable + Reference Type car.dashboard = Dashboard(speed: 99, rpm: car.dashboard.rpm)
Heap과�Reference�Counting�
또�컴파일러�최적화가�어려움
API가�이상해지는�경우도
// 속도가 변경되었다
// Mutable + Value Type car.dashboard.speed = 99
// Immutable + Reference Type car.dashboard = Dashboard(speed: 99, rpm: car.dashboard.rpm)
그것도�그렇지만,�
Dashboard를�바꾼�다는�의미인건가?
그래도�class도�중요한�경우
Value보단�Identity가�중요한�경우�
• UIView�같이�모든�변수에서�단�하나의�state를�갖는�개체�
OOP�모델�
• 여전히�상속은�아주�훌륭한�도구�
Objective-C�연동�
Indirect�storage�(특수한�경우�struct내의�간접�저장소�역할)�
• 뒤에서�설명
let swift(16)
성능을�위해�고려할�것들
성능에�영향을�미치는�3가지
Memory�Allocation:�Stack�or�Heap�
Reference�Counting:�No�or�Yes�
Method�dispatch:�Static�or�Dynamic
Heap�할당의�문제
할당시에�빈�곳을�찾고�관리하는�것은�복잡한�과정�
Heap
Heap�할당의�문제
할당시에�빈�곳을�찾고�관리하는�것은�복잡한�과정�
무엇보다�그�과정이�thread�safe해야한다는�점이�가장�큰�문제�
• ��lock�등의�synchronization�동작은�큰�성능�저하�요소�
반면�Stack�할당은�
• 단순히�스택포인터�변수값만�바꿔주는�정도
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으로�바꿔보자!
Heap�할당�줄이기
struct Attribute { var color: Color var theme: Theme var selected: Bool }
새로운�Key�타입�정의
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가�되려면
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 }
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�할당�오버헤드�없음
Reference�Counting의�문제
정말�자주�실행된다�
• 변수�Copy할�때�마다�
그러나�이것도�역시�가장�큰�문제는�thread�safety�때문�
• 카운트를�Atomic하게�늘리고�줄여야함
class MyClass { }
func foo(c: MyClass) {
…
}
do { let c0: MyClass = MyClass()
var c1: MyClass? = c0
foo(c0) c1 = nil
}
Reference�Counting의�동작
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
Heapc0
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
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
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
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
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
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�
손으로�다�넣던�시절이�있었습니다…�
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�매우�빈번한�것
Method�Dispatch�(Static)
컴파일�시점에�메소드의�실제�코드�위치를�안다면�
실행중�찾는�과정�없이�바로�해당�코드�주소로�점프할�수�있음�
컴파일러의�최적화,�메소드�인라이닝�(Inlining)�가능
메소드�인라이닝
컴파일�시점에�메소드��호출�부분에�메소드�내용을�붙여넣음�
• 효과가�있다고�판단되는�경우에만�
Call�stack�오버헤드�줄임�
• CPU�icache나�레지스터를�효율적으로�쓸�가능성�
컴파일러의�추가�최적화�가능�
• 최근�메소드들이�작으므로�더더욱�기회가�많음�
• 루프�안에서�불리는�경우�큰�효과
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)
메소드�인라이닝
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)
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)
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)
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)
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단계의�호출이�줄었다�
두�코드가�붙어�추가적인�최적화의�기회도�생겼다
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)��
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?�
어떻게�알지?
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
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:
…
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의�코드�주소를�알아내어
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:
…
호출한다
Dynamic�Method�Dispatch의�문제
요점은,�실제�Type을�컴파일�시점에�알�수가�없다는�것�
때문에,�코드�주소를�runtime에�찾아야�한다�
Static에�비해�단지�이것이�문제.�Thread�saftety문제도�없다�
하지만�이로�인해�컴파일러가�최적화를�못하는�것이�큰�문제
Objective-C
Objective-C의�method�dispatch는�Message�sending�방식�
[anObject doMethod:aParameter];
아래처럼�동적으로�메소드를�Lookup하여�호출된다.�
objc_msgSend(anObject, @selector(doMethod:), aParameter);
강력하고�유연한�특징을�가지고�있지만�성능�저하�요소�
특히�Loop안에서�빈번하게�Method�호출이�일어나는�경우
Static�Dispatch로�강제하기
final,�private�등을�쓰는�버릇�
• 해당�메소드,�프로퍼티등은�상속�안�되므로�static하게�처리�
dynamic�키워드�최소화�
Objc�연동�최소화�
• Objective-C�Runtime을�통하게�됨�
WMO�(whole�module�optimization)
Whole�Module�Optimization
빌드시에�모든�파일을�한번에�분석하여,�
static�dispatch로�변환��가능한지�등을�판단하여�최적화�
Whole�Module�Optimization
빌드시에�모든�파일을�한번에�분석하여,�
static�dispatch로�변환��가능한지�등을�판단하여�최적화�
Whole�Module�Optimization
빌드시에�모든�파일을�한번에�분석하여,�
static�dispatch로�변환��가능한지�등을�판단하여�최적화��
겁나�느려짐�주의�(Xcode7)�
디버그�빌드에�적용하는�것은�정신�건강에�좋지�않습니다�
아직�안정화가�안�됨�주의�(Xcode7)��너무�믿진�마세요…
정리:�성능에�영향을�미치는�3가지
Memory�Allocation:�Stack�or�Heap�
Reference�Counting:�No�or�Yes�
Method�Dispatch:�Static�or�Dynamic
let swift(16)
스위프트의�추상화�기법들과�성능
추상화�기법들
Class�
Struct�
Protocol�Type�
Generics�Type�
각각�앞서�소개한�성능�요소들에�대해�어떤�특징을�가지는가
class
class Point { var x: CGFloat var y: CGFloat }
let c1 = Point(x: 0.0, y: 0.0)
let c2 = c1
…
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)
Stackc1:c2:
Heap…refCount: 2x: 0.0y: 0.0
Heap,�Reference�Counting�사용
class
Memory�Allocation:�Heap�
Reference�Counting:�Yes�
Method�Dispatch:�Dynamic�(V-Table)�
• 성능�상관�없이�레퍼런스�시맨틱스가�필요하다면�써야함�
• Identity,�상속,�…�
• 단�레퍼런스의�의도하치�공유로�인한�문제�조심
final�class
Memory�Allocation:�Heap�
Reference�Counting:�Yes�
Method�Dispatch:�Static
참조�타입이�없는�struct
struct Point { var x: CGFloat var y: CGFloat }
let c1 = Point(x: 0.0, y: 0.0)
let c2 = c1
참조�타입이�없는�struct
struct Point { var x: CGFloat var y: CGFloat }
let c1 = Point(x: 0.0, y: 0.0)
let c2 = c1
Stackc1: x: 0.0
y: 0.0
참조�타입이�없는�struct
struct Point { var x: CGFloat var y: CGFloat }
let c1 = Point(x: 0.0, y: 0.0)
let c2 = c1
Stackc1: x: 0.0
y: 0.0c2: x: 0.0
y: 0.0
참조�타입이�없는�struct
Memory�Allocation:�Stack�
Reference�Counting:�No�
Method�Dispatch:�Static
참조�타입을�가진�struct
struct Label { var text: String var font: UIFont }
let c1 = Label(text: “msg”, font: font)
let c2 = c1
…
참조�타입을�가진�struct
struct Label { var text: String var font: UIFont }
let c1 = Label(text: “msg”, font: font)
let c2 = c1
…
class�type
참조�타입을�가진�struct
struct Label { var text: String var font: UIFont }
let c1 = Label(text: “msg”, font: font)
let c2 = c1
…
class�type
value�type�…?
참조�타입을�가진�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�등도�마찬가지)
참조�타입을�가진�struct
struct Label { var text: String var font: UIFont }
let c1 = Label(text: “msg”, font: font)
let c2 = c1
…
참조�타입을�가진�struct
struct Label { var text: String var font: UIFont }
let c1 = Label(text: “msg”, font: font)
let c2 = c1
…
Stackc1: text:
… _storagefont:
Heap…refCount: 1…
…refCount: 1…
참조�타입을�가진�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) …
Stackc1: text:
… _storagefont:
c2: text: … _storage
font:
Heap…refCount: 2…
…refCount: 2…
참조�타입을�가진�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: … _storagefont:
c2:text: … _storage
font:
Heap…refCount: 0…
…refCount: 0…
참조�타입을�가진�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�수만큼�많아진다.
참조�타입을�가진�struct
Memory�Allocation:�Stack�
Reference�Counting:�Yes�
Method�Dispatch:�Static
참조�타입이�많은�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 }
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
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로�몰아�넣기
Protocol�Type
코드�없이�API만�정의함�
상속�없는�다형성�(Polymorphism)�구현이�가능��
Objective�C의�protocol,�Java의�Interface�매우�유사함�
Value�type인�struct에도�적용이�가능하다�
• Value��semantics에서의�다형성
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으로
실제�메소드�호출
의문점:�변수�할당
class라면�주소값이니�모두�같은�사이즈지만,�
struct인�Point와�Line은�사이즈가�다르다.�
어떻게�Drawable에�메모리를�미리�할당해�놓고�값을�저장할까?
struct Point : Drawable { var x, y: CGFloat … } struct Line : Drawable { var x1, y1, x2, y2: CGFloat … }
의문점:�Method�Dispatch
class의�다형성�구조에선�V-Table을�통해서�찾았다.�
상속이�아닌�Protocol의�다형성�구조에선�V-Table이�없다�
어떻게�Point.draw와�Line.draw를�구분해서�호출할까?
var drawables: [Drawable] … for d in drawables { d.draw() }
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 ? ? ?
[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() }
Pointx: 0.0y: 0.0
Linex1: 0.0y1: 0.0x2: 1.0y2: 1.0
refCount ? ? ?
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…
Pointx: 0.0y: 0.0
Linex1: 0.0y1: 0.0x2: 1.0y2: 1.0
Existential�Container
Value�Buffer�
(3�words)
Protocol�type의�실제�값을�넣고�관리하는�구조
(1�word는�32bit�CPU에서는�32bit,�64bit�CPU에서는�64bit)
(Fixed�size)
Existential�Container
Drawable Existence x: 0.0y: 0.0
struct가�3�words�이하인�경우
Pointx: 0.0y: 0.0
Existential�container�안에�값�모두�저장됨
Existential�Container
Drawableref
Linex1: 0.0y1: 0.0x2: 1.0y2: 1.0
Existential�Container
struct가�3�words보다�큰�경우
x1: 0.0y1: 0.0x2: 1.0y2: 1.0
HeapExistential�Container
Heap�할당하여�값�저장�
Existential�container에�해당�레퍼런스�저장
어떻게�3�word를�구분해�할당하고�복사하는가?�
Value�Witness�Table�(VWT)
VWTallocate:copy:destruct:deallocate:
Existential�container의�생성/해제를�담당하는�인터페이스
Value�Witness�Table�(VWT)
Line VWTallocate:copy:destruct:deallocate:
Point VWTallocate:copy:destruct:deallocate:
Protocol을�구현하는�type마다�있다
Drawable
Drawable
Value�Witness�Table�(VWT)
Line VWTallocate:copy:destruct:deallocate:
Point VWTallocate:copy:destruct:deallocate:
실제�변수�영역
Existential�Container
Line VWTallocate:copy:destruct:deallocate:
Point VWTallocate:copy:destruct:deallocate:
Drawable
Drawableref:
Value�Witness�Table�(VWT)
Heap
Existential�Container
Line VWTallocate:copy:destruct:deallocate:
Point VWTallocate:copy:destruct:deallocate:
Drawablex: 0.0y: 0.0
Drawableref:
Value�Witness�Table�(VWT)
x1: 0.0y1: 0.0x2: 1.0y2: 1.0
Heap
Existential�Container
Line VWTallocate:copy:destruct:deallocate:
Point VWTallocate:copy:destruct:deallocate:
Drawable
Drawableref:
Value�Witness�Table�(VWT)
Heap
Existential�Container
Line VWTallocate:copy:destruct:deallocate:
Point VWTallocate:copy:destruct:deallocate:
Drawable
Drawable
Value�Witness�Table�(VWT)
Existential�Container
Drawablex: 0.0y: 0.0
vwt:
Drawableref:
vwt:
Value�Witness�Table�(VWT)
x1: 0.0y1: 0.0x2: 1.0y2: 1.0
Heap
Line VWTallocate:copy:destruct:deallocate:
Point VWTallocate:copy:destruct:deallocate:
Existential�Container
Drawablex: 0.0y: 0.0
vwt:
Drawableref:
vwt:
Method�Dispatch는?
x1: 0.0y1: 0.0x2: 1.0y2: 1.0
Heap
Protocol�Witness�Table
Line VWTallocate:copy:destruct:deallocate:
Point VWTallocate:copy:destruct:deallocate:
Point Drawabledraw:…
Line Drawabledraw:…
Existential�Container
Point Drawabledraw:…
Line Drawabledraw:…
Drawablex: 0.0y: 0.0
vwt:
Drawableref:
vwt:
Method�Dispatch는?
x1: 0.0y1: 0.0x2: 1.0y2: 1.0
Heap
Line VWTallocate:copy:destruct:deallocate:
Point VWTallocate:copy:destruct:deallocate:
Existential�ContainerProtocol�Witness�Table
Drawablex: 0.0y: 0.0
vwt: pwt:
Drawableref:
vwt: pwt:
Method�Dispatch는?
Line VWTallocate:copy:destruct:deallocate:
x1: 0.0y1: 0.0x2: 1.0y2: 1.0
Heap
Point VWTallocate:copy:destruct:deallocate:
Point Drawabledraw:…
Line Drawabledraw:…Dynamic Method Dispatch
Existential�ContainerProtocol�Witness�Table
Copy�동작�정리
Value�타입이므로�값�전체가�Copy된다.�
3�words�이하의�경우�
• 단순히�새로운�Existential�container에�전체가�복사됨�
3�words를�넘는�경우�
• 새로운�Existential�container�생성�
• 값�전체가�새로운�Heap할당�후�복사됨
큰�사이즈�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
Drawableline: ref:
vwt: pwt:
큰�사이즈�protocol�타입의�copy
x1: 0.0y1: 0.0x2: 0.0y2: 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
Drawableline: ref:
vwt: pwt:
큰�사이즈�protocol�타입의�copy
x1: 0.0y1: 0.0x2: 0.0y2: 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 Drawablecopy: ref:
vwt: pwt:
x1: 0.0y1: 0.0x2: 0.0y2: 0.0
copy
Drawableline: ref:
vwt: pwt:
큰�사이즈�protocol�타입의�copy
x1: 0.0y1: 0.0x2: 0.0y2: 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 Drawablecopy: ref:
vwt: pwt:
x1: 0.0y1: 0.0x2: 0.0y2: 0.0
copy
Heap의�데이터도�복사가된다!�
Drawableline: ref:
vwt: pwt:
큰�사이즈�protocol�타입의�copy
x1: 0.0y1: 0.0x2: 0.0y2: 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 Drawablecopy: ref:
vwt: pwt:
x1: 0.0y1: 0.0x2: 1.0y2: 0.0
copy
Heap의�데이터도�복사가된다!�
나름�Value�type이니까!�
Heap은�쓰지만�Reference�counting이�없다
Drawableline: ref:
vwt: pwt:
큰�사이즈�protocol�타입의�copy
x1: 0.0y1: 0.0x2: 0.0y2: 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 Drawablecopy: ref:
vwt: pwt:
copy
Copy마다�새로운�Heap�할당하는데�이것이��큰�성능�저하�요소!
x1: 0.0y1: 0.0x2: 1.0y2: 0.0
개선해�봅시다
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
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�타입의�간접�저장소로�이동
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
Drawableline: _storage:
vwt: pwt:
Heap
…refCount: 1x1: 0.0y1: 0.0x2: 0.0y2: 0.0
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
Drawableline: _storage:
vwt: pwt:
Heap
Drawablecopy: _storage:
vwt: pwt:
…refCount: 2x1: 0.0y1: 0.0x2: 0.0y2: 0.0
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: 2x1: 0.0y1: 0.0x2: 0.0y2: 0.0
Drawableline: _storage:
vwt: pwt:
Drawablecopy: _storage:
vwt: pwt:
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: 2x1: 0.0y1: 0.0x2: 0.0y2: 0.0
하지만�값을�바꾼다면?
Drawableline: _storage:
vwt: pwt:
Drawablecopy: _storage:
vwt: pwt:
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: 2x1: 0.0y1: 0.0x2: 1.0y2: 0.0
둘�다�바뀌어�버림!
Drawableline: _storage:
vwt: pwt:
Drawablecopy: _storage:
vwt: pwt:
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: 2x1: 0.0y1: 0.0x2: 0.0y2: 0.0
Drawableline: _storage:
vwt: pwt:
Drawablecopy: _storage:
vwt: pwt:
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: 1x1: 0.0y1: 0.0x2: 0.0y2: 0.0
…refCount: 1x1: 0.0y1: 0.0x2: 0.0y2: 0.0
Drawableline: _storage:
vwt: pwt:
Drawablecopy: _storage:
vwt: pwt:
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: 1x1: 0.0y1: 0.0x2: 0.0y2: 0.0
…refCount: 1x1: 0.0y1: 0.0x2: 1.0y2: 0.0
Drawableline: _storage:
vwt: pwt:
Drawablecopy: _storage:
vwt: pwt:
Existential�Container
변수가�Protocol�type으로�정의된�경우�쓰임�
프로토콜을�통한�다형성을�구현하기�위한�목적으로�쓰임�
내부�동작이�복잡하긴해도�성능이�class�쓰는것과�비슷하다�
• 둘�다�초기화�시�Heap�할당하여�사용�
• 둘�다�Dynamic�dispatch�(class도�V-Table,�protocol은�PWT)
큰�사이즈�protocol�타입의�copy
Indirect�Storage�
• Copy시�Heap�할당�대신�Reference�counting으로�대체�
• class타입의�다형성�쓸때와�비슷한�수준�
Copy-on-Write�
• Indirect�storage를�값이�변경될�시점에�Heap�할당하여�복사�
• 성능�저하를�최소화�함�(변경�동작에서만)�
String,�Array,�Dictionary�등도�이런�개념으로�Value�semantics�구현
작은�사이즈의�Protocol�Type
Memory�Allocation:�Stack�
Reference�Counting:�No�
Method�Dispatch:�Dynamic�(Protocol�Witness�Table)
큰�사이즈의�Protocol�Type
Memory�Allocation:�MANY!�(Copy할�때마다�할당)�
Reference�Counting:�No�(class�프로퍼티가�있을�때만)�
Method�Dispatch:�Dynamic�(Protocol�Witness�Table)
큰�사이즈의�Protocol�Type
Memory�Allocation:�Heap�
Reference�Counting:�Yes�
Method�Dispatch:�Dynamic�(Protocol�Witness�Table)
with�Indirect�Storage
Generics�Type
protocol Drawable { func draw() } func drawACopy<T: Drawable>(local: T) { local.draw() } drawACopy(Point(…))
drawACopy(Line(…))
Generics�Type
protocol Drawable { func draw() } func drawACopy<T: Drawable>(local: T) { local.draw() } drawACopy(Point(…))
drawACopy(Line(…))
Point VWTallocate:copy:destruct:deallocate:
Drawablelocal: x: 0.0
y: 0.0
vwt:pwt:
VWT�이용하여�값�복사
Generics�Type
protocol Drawable { func draw() } func drawACopy<T: Drawable>(local: T) { local.draw() } drawACopy(Point(…))
drawACopy(Line(…))
Line VWTallocate:copy:destruct:deallocate:
Drawablelocal: ref
vwt:pwt:
VWT�이용하여��
메모리�할당,�값�복사
x1: 0.0y1: 0.0x2: 0.0y2: 0.0
Generics�Type
protocol Drawable { func draw() } func drawACopy<T: Drawable>(local: T) { local.draw() } drawACopy(Point(…))
drawACopy(Line(…))
Drawablelocal: x: 0.0
y: 0.0
vwt:pwt:
Point Drawabledraw:…
Dynamic�Method�Dispatch
Generics�Type
protocol Drawable { func draw() } func drawACopy<T: Drawable>(local: T) { local.draw() } drawACopy(Point(…))
drawACopy(Line(…))
Drawablelocal: x: 0.0
y: 0.0
vwt:pwt:
Point Drawabledraw:…
Dynamic�Method�Dispatch�
성능�개선할�수�있을까?
Generics�Type
protocol Drawable { func draw() } func drawACopy<T: Drawable>(local: T) { local.draw() } drawACopy(Point(…))
drawACopy(Line(…))
정적 다형성 (Static Polymorphism)
Method�내에서는�Drawable의�
실제�타입이�바뀌지�않는다
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�특수화)
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�특수화)
이걸�손으로�하면,�Generics�쓰지�말란�말?
Generic�특수화�(Specialization)
컴파일러가�해�줍니다.�
더�효과를�보려면�WMO�(Whole�Module�Optimization)�이용
아직�너무�믿진�마세요.�(Xcode�7)
Generics�Type�정리
정적�다형성�(Static�Polymorphism)�
• 컴파일�시점에�부르는�곳마다�타입이�정해져�있음�
• 런타임에�바뀌지�않음�
• 특수화�(Specialization)가�가능�
특수화�되지�않은�Generics��(작은�사이즈의�Protocol�Type)
Memory�Allocation:�Stack�
Reference�Counting:�No�
Method�Dispatch:�Dynamic�(Protocol�Witness�Table)
특수화된�Generics�Type�(struct)
Memory�Allocation:�Stack�
Reference�Counting:�No�
Method�Dispatch:�Static
특수화�되지�않은�Generics��(큰�사이즈의�Protocol�Type)
Memory�Allocation:�MANY!�(Copy할�때마다�할당)�
Reference�Counting:�No�(class�프로퍼티가�있을�때만)�
Method�Dispatch:�Dynamic�(Protocol�Witness�Table)
특수화된�Generics�Type�(class)
Memory�Allocation:�Heap�
Reference�Counting:�Yes�
Method�Dispatch:�Dynamic�(V-Table)
let swift(16)
정리
스위프트의�성능
Objective-C에�비해�큰�향상이�있었으나�
Value�타입과�Protocol�타입�등의�성격을�고려해야�함�
성능�최적화를�고려해야하는�경우의�예�
• 렌더링�관련�로직�등�반복적으로�매우�빈번히�불리는�경우��
• 서버�환경에서의�대용량�데이터�처리
추상화�기법의�선택
Struct:�엔티티�등�Value�시맨틱이�맞는�부분�
Class:�Identity가�필요한�부분,�상속등의�OOP,�Objective-C�
Generics:�정적�다형성으로�가능한�경우�
Protocol:�동적�다형성이�필요한�경우�
고려할�수�있는�성능�최적화�기법들
Struct에�클래스�타입의�Property가�많으면�
• enum,�struct등�Value�type으로�대체��
• Reference�counting�줄임�
Protocol�Type을�쓸�때�대상이�큰�struct면�
• Indirect�storage로�struct�구조�변경�
• Mutable해야하면�Copy-on-Write�구현
고려할�수�있는�성능�최적화�기법들
Dynamic�method�dispatch를�static하게�
• final,�private의�생활화�
• dynamic�사용�최소화�
• Objc�연동�최소화�하기�
• 릴리즈�빌드에�WMO�옵션�적용�고려
마지막으로
정답은�없습니다.�
잘�된�디자인을�해치면서까지,�
모든�경우에�반드시�적용�해야하는�것은�아닙니다.�
돌아가는�환경,�데이터의�특성과�다루는�양�등에�따라�다릅니다.�
하지만�배경을�알면�옳은�방향으로�향할�수가�있습니다.
참고
WWDC 2016
• Session 416: Understanding Swift Performance
WWDC 2015
• Session 409: Optimizing Swift Performance
• Session 414: Building Better Apps with Value Types in Swift
let swift(16)