SlideShare ist ein Scribd-Unternehmen logo
1 von 158
Downloaden Sie, um offline zu lesen
let swift(
스위프트 성능 이해하기
Value 타입, Protocol과 스위프트의 성능 최적화
@inkyfox유용하
내용
Value Semantics
성능을 위해 고려할 것들
스위프트의 추상화 기법들과 성능
let swift(
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
Stack
c1: ref
Heap
…
refCount: 1
x: 0.0
y: 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
Stack
c1: ref
c2: ref
Heap
…
refCount: 2
x: 0.0
y: 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
Stack
c1: ref
c2: ref
Heap
…
refCount: 2
x: 0.0
y: 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
Stack
c1: 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
Stack
c1: x: 0.0
y: 0.0
c2: 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
Stack
c1: x: 0.0
y: 0.0
c2: 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(
성능을 위해 고려할 것들
성능에 영향을 미치는 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
Heap
c0
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(
스위프트의 추상화 기법들과 성능
추상화 기법들
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)
Stack
c1:
c2:
Heap
…
refCount: 2
x: 0.0
y: 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
Stack
c1: 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
Stack
c1: x: 0.0
y: 0.0
c2: 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
…
Stack
c1: text:
…
_storage
font:
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)
…
Stack
c1: text:
…
_storage
font:
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:
…
_storage
font:
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()
}
Point
x: 0.0
y: 0.0
Line
x1: 0.0
y1: 0.0
x2: 1.0
y2: 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
…
Point
x: 0.0
y: 0.0
Line
x1: 0.0
y1: 0.0
x2: 1.0
y2: 1.0
Existential Container
Value Buffer
(3 words)
Protocol type의 실제 값을 넣고 관리하는 구조
(1 word는 32bit CPU에서는 32bit, 64bit CPU에서는 64bit)
(Fixed size)
Existential Container
Drawable
Existencex: 0.0
y: 0.0
struct가 3 words 이하인 경우
Point
x: 0.0
y: 0.0
Existential container 안에 값 모두 저장됨
Existential Container
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에 해당 레퍼런스 저장
어떻게 3 word를 구분해 할당하고 복사하는가?
Value Witness Table (VWT)
VWT
allocate:
copy:
destruct:
deallocate:
Existential container의 생성/해제를 담당하는 인터페이스
Value Witness Table (VWT)
Line VWT
allocate:
copy:
destruct:
deallocate:
Point VWT
allocate:
copy:
destruct:
deallocate:
Protocol을 구현하는 type마다 있다
Drawable
Drawable
Value Witness Table (VWT)
Line VWT
allocate:
copy:
destruct:
deallocate:
Point VWT
allocate:
copy:
destruct:
deallocate:
실제 변수 영역
Existential Container
Line VWT
allocate:
copy:
destruct:
deallocate:
Point VWT
allocate:
copy:
destruct:
deallocate:
Drawable
Drawable
ref:
Value Witness Table (VWT)
Heap
Existential Container
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
Line VWT
allocate:
copy:
destruct:
deallocate:
Point VWT
allocate:
copy:
destruct:
deallocate:
Drawable
Drawable
ref:
Value Witness Table (VWT)
Heap
Existential Container
Line VWT
allocate:
copy:
destruct:
deallocate:
Point VWT
allocate:
copy:
destruct:
deallocate:
Drawable
Drawable
Value Witness Table (VWT)
Existential Container
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
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
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
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
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
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
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
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의 데이터도 복사가된다!
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이 없다
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
개선해 봅시다
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
Drawable
line: _storage:
vwt:
pwt:
Heap
…
refCount: 1
x1: 0.0
y1: 0.0
x2: 0.0
y2: 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
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
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:
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:
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:
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:
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:
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:
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 VWT
allocate:
copy:
destruct:
deallocate:
Drawable
local: 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 VWT
allocate:
copy:
destruct:
deallocate:
Drawable
local: ref
vwt:
pwt:
VWT 이용하여
메모리 할당, 값 복사
x1: 0.0
y1: 0.0
x2: 0.0
y2: 0.0
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
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
성능 개선할 수 있을까?
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(
정리
스위프트의 성능
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)

Weitere ähnliche Inhalte

Was ist angesagt?

Angular Best Practices To Build Clean and Performant Web Applications
Angular Best Practices To Build Clean and Performant Web ApplicationsAngular Best Practices To Build Clean and Performant Web Applications
Angular Best Practices To Build Clean and Performant Web ApplicationsAlbiorix Technology
 
React + Redux Introduction
React + Redux IntroductionReact + Redux Introduction
React + Redux IntroductionNikolaus Graf
 
The New JavaScript: ES6
The New JavaScript: ES6The New JavaScript: ES6
The New JavaScript: ES6Rob Eisenberg
 
ASP.NET Core MVC + Web API with Overview
ASP.NET Core MVC + Web API with OverviewASP.NET Core MVC + Web API with Overview
ASP.NET Core MVC + Web API with OverviewShahed Chowdhuri
 
Efficient, maintainable CSS
Efficient, maintainable CSSEfficient, maintainable CSS
Efficient, maintainable CSSRuss Weakley
 
Clean Code II - Dependency Injection
Clean Code II - Dependency InjectionClean Code II - Dependency Injection
Clean Code II - Dependency InjectionTheo Jungeblut
 
MVC, MVVM, ReactorKit, VIPER를 거쳐 RIB 정착기
MVC, MVVM, ReactorKit, VIPER를 거쳐 RIB 정착기MVC, MVVM, ReactorKit, VIPER를 거쳐 RIB 정착기
MVC, MVVM, ReactorKit, VIPER를 거쳐 RIB 정착기정민 안
 
Angular performance slides
Angular performance slidesAngular performance slides
Angular performance slidesDavid Barreto
 
Angular and The Case for RxJS
Angular and The Case for RxJSAngular and The Case for RxJS
Angular and The Case for RxJSSandi Barr
 
Angular Introduction By Surekha Gadkari
Angular Introduction By Surekha GadkariAngular Introduction By Surekha Gadkari
Angular Introduction By Surekha GadkariSurekha Gadkari
 
Introduction to Javascript
Introduction to JavascriptIntroduction to Javascript
Introduction to JavascriptAmit Tyagi
 
Introduction to SASS
Introduction to SASSIntroduction to SASS
Introduction to SASSJon Dean
 

Was ist angesagt? (20)

Angular Best Practices To Build Clean and Performant Web Applications
Angular Best Practices To Build Clean and Performant Web ApplicationsAngular Best Practices To Build Clean and Performant Web Applications
Angular Best Practices To Build Clean and Performant Web Applications
 
React Hooks
React HooksReact Hooks
React Hooks
 
React + Redux Introduction
React + Redux IntroductionReact + Redux Introduction
React + Redux Introduction
 
The New JavaScript: ES6
The New JavaScript: ES6The New JavaScript: ES6
The New JavaScript: ES6
 
Introduction to Redux
Introduction to ReduxIntroduction to Redux
Introduction to Redux
 
Introduction to React JS
Introduction to React JSIntroduction to React JS
Introduction to React JS
 
Angular modules in depth
Angular modules in depthAngular modules in depth
Angular modules in depth
 
ASP.NET Core MVC + Web API with Overview
ASP.NET Core MVC + Web API with OverviewASP.NET Core MVC + Web API with Overview
ASP.NET Core MVC + Web API with Overview
 
Efficient, maintainable CSS
Efficient, maintainable CSSEfficient, maintainable CSS
Efficient, maintainable CSS
 
Angular Data Binding
Angular Data BindingAngular Data Binding
Angular Data Binding
 
Clean Code II - Dependency Injection
Clean Code II - Dependency InjectionClean Code II - Dependency Injection
Clean Code II - Dependency Injection
 
Angular 9
Angular 9 Angular 9
Angular 9
 
MVC, MVVM, ReactorKit, VIPER를 거쳐 RIB 정착기
MVC, MVVM, ReactorKit, VIPER를 거쳐 RIB 정착기MVC, MVVM, ReactorKit, VIPER를 거쳐 RIB 정착기
MVC, MVVM, ReactorKit, VIPER를 거쳐 RIB 정착기
 
Angular performance slides
Angular performance slidesAngular performance slides
Angular performance slides
 
Angular and The Case for RxJS
Angular and The Case for RxJSAngular and The Case for RxJS
Angular and The Case for RxJS
 
Angular Introduction By Surekha Gadkari
Angular Introduction By Surekha GadkariAngular Introduction By Surekha Gadkari
Angular Introduction By Surekha Gadkari
 
Introduction to Javascript
Introduction to JavascriptIntroduction to Javascript
Introduction to Javascript
 
React Hooks
React HooksReact Hooks
React Hooks
 
Git basic
Git basicGit basic
Git basic
 
Introduction to SASS
Introduction to SASSIntroduction to SASS
Introduction to SASS
 

Andere mochten auch

RxSwift 예제로 감잡기
RxSwift 예제로 감잡기RxSwift 예제로 감잡기
RxSwift 예제로 감잡기Yongha Yoo
 
Swift and Xcode8
Swift and Xcode8Swift and Xcode8
Swift and Xcode8Hyuk Hur
 
Letswift Swift 3.0
Letswift Swift 3.0Letswift Swift 3.0
Letswift Swift 3.0Sehyun Park
 
Do swift: Swift 무작정 해보기
Do swift: Swift 무작정 해보기Do swift: Swift 무작정 해보기
Do swift: Swift 무작정 해보기YoonBong Steve Kim
 
Swift package manager
Swift package managerSwift package manager
Swift package manager성관 윤
 
안드로이드 개발자를 위한 스위프트
안드로이드 개발자를 위한 스위프트안드로이드 개발자를 위한 스위프트
안드로이드 개발자를 위한 스위프트병한 유
 
Swift server-side-let swift2016
Swift server-side-let swift2016Swift server-side-let swift2016
Swift server-side-let swift2016Eric Ahn
 
Protocol Oriented Programming in Swift
Protocol Oriented Programming in SwiftProtocol Oriented Programming in Swift
Protocol Oriented Programming in SwiftSeongGyu Jo
 
Swift internals
Swift internalsSwift internals
Swift internalsJung Kim
 
LetSwift RxSwift 시작하기
LetSwift RxSwift 시작하기LetSwift RxSwift 시작하기
LetSwift RxSwift 시작하기Wanbok Choi
 

Andere mochten auch (10)

RxSwift 예제로 감잡기
RxSwift 예제로 감잡기RxSwift 예제로 감잡기
RxSwift 예제로 감잡기
 
Swift and Xcode8
Swift and Xcode8Swift and Xcode8
Swift and Xcode8
 
Letswift Swift 3.0
Letswift Swift 3.0Letswift Swift 3.0
Letswift Swift 3.0
 
Do swift: Swift 무작정 해보기
Do swift: Swift 무작정 해보기Do swift: Swift 무작정 해보기
Do swift: Swift 무작정 해보기
 
Swift package manager
Swift package managerSwift package manager
Swift package manager
 
안드로이드 개발자를 위한 스위프트
안드로이드 개발자를 위한 스위프트안드로이드 개발자를 위한 스위프트
안드로이드 개발자를 위한 스위프트
 
Swift server-side-let swift2016
Swift server-side-let swift2016Swift server-side-let swift2016
Swift server-side-let swift2016
 
Protocol Oriented Programming in Swift
Protocol Oriented Programming in SwiftProtocol Oriented Programming in Swift
Protocol Oriented Programming in Swift
 
Swift internals
Swift internalsSwift internals
Swift internals
 
LetSwift RxSwift 시작하기
LetSwift RxSwift 시작하기LetSwift RxSwift 시작하기
LetSwift RxSwift 시작하기
 

Ähnlich wie 스위프트 성능 이해하기

Scala, Scalability
Scala, ScalabilityScala, Scalability
Scala, ScalabilityDongwook Lee
 
Let's Go (golang)
Let's Go (golang)Let's Go (golang)
Let's Go (golang)상욱 송
 
[Main Session] 미래의 Java 미리보기 - 앰버와 발할라 프로젝트를 중심으로
[Main Session] 미래의 Java 미리보기 - 앰버와 발할라 프로젝트를 중심으로[Main Session] 미래의 Java 미리보기 - 앰버와 발할라 프로젝트를 중심으로
[Main Session] 미래의 Java 미리보기 - 앰버와 발할라 프로젝트를 중심으로Oracle Korea
 
12장 상속 (고급)
12장 상속 (고급)12장 상속 (고급)
12장 상속 (고급)유석 남
 
NDC11_슈퍼클래스
NDC11_슈퍼클래스NDC11_슈퍼클래스
NDC11_슈퍼클래스noerror
 
Effective c++(chapter 5,6)
Effective c++(chapter 5,6)Effective c++(chapter 5,6)
Effective c++(chapter 5,6)문익 장
 
Javascript 조금 더 잘 알기
Javascript 조금 더 잘 알기Javascript 조금 더 잘 알기
Javascript 조금 더 잘 알기jongho jeong
 
Programming skills 1부
Programming skills 1부Programming skills 1부
Programming skills 1부JiHyung Lee
 
Api design for c++ 6장
Api design for c++ 6장Api design for c++ 6장
Api design for c++ 6장Ji Hun Kim
 
스파르탄스터디 E04 Javascript 객체지향, 함수형 프로그래밍
스파르탄스터디 E04 Javascript 객체지향, 함수형 프로그래밍스파르탄스터디 E04 Javascript 객체지향, 함수형 프로그래밍
스파르탄스터디 E04 Javascript 객체지향, 함수형 프로그래밍Young-Beom Rhee
 
PySpark 배우기 Ch 06. ML 패키지 소개하기
PySpark 배우기 Ch 06. ML 패키지 소개하기PySpark 배우기 Ch 06. ML 패키지 소개하기
PySpark 배우기 Ch 06. ML 패키지 소개하기찬희 이
 
일단 시작하는 코틀린
일단 시작하는 코틀린일단 시작하는 코틀린
일단 시작하는 코틀린Park JoongSoo
 
C++ Advanced 강의 4주차
 C++ Advanced 강의 4주차 C++ Advanced 강의 4주차
C++ Advanced 강의 4주차HyunJoon Park
 

Ähnlich wie 스위프트 성능 이해하기 (20)

ES6 for Node.js Study 2주차
ES6 for Node.js Study 2주차ES6 for Node.js Study 2주차
ES6 for Node.js Study 2주차
 
C++에서 Objective-C까지
C++에서 Objective-C까지C++에서 Objective-C까지
C++에서 Objective-C까지
 
Scala, Scalability
Scala, ScalabilityScala, Scalability
Scala, Scalability
 
Scalability
ScalabilityScalability
Scalability
 
Let's Go (golang)
Let's Go (golang)Let's Go (golang)
Let's Go (golang)
 
[Main Session] 미래의 Java 미리보기 - 앰버와 발할라 프로젝트를 중심으로
[Main Session] 미래의 Java 미리보기 - 앰버와 발할라 프로젝트를 중심으로[Main Session] 미래의 Java 미리보기 - 앰버와 발할라 프로젝트를 중심으로
[Main Session] 미래의 Java 미리보기 - 앰버와 발할라 프로젝트를 중심으로
 
Java.next
Java.nextJava.next
Java.next
 
12장 상속 (고급)
12장 상속 (고급)12장 상속 (고급)
12장 상속 (고급)
 
6 function
6 function6 function
6 function
 
NDC11_슈퍼클래스
NDC11_슈퍼클래스NDC11_슈퍼클래스
NDC11_슈퍼클래스
 
Effective c++(chapter 5,6)
Effective c++(chapter 5,6)Effective c++(chapter 5,6)
Effective c++(chapter 5,6)
 
Javascript 조금 더 잘 알기
Javascript 조금 더 잘 알기Javascript 조금 더 잘 알기
Javascript 조금 더 잘 알기
 
Programming skills 1부
Programming skills 1부Programming skills 1부
Programming skills 1부
 
06장 함수
06장 함수06장 함수
06장 함수
 
Api design for c++ 6장
Api design for c++ 6장Api design for c++ 6장
Api design for c++ 6장
 
강의자료4
강의자료4강의자료4
강의자료4
 
스파르탄스터디 E04 Javascript 객체지향, 함수형 프로그래밍
스파르탄스터디 E04 Javascript 객체지향, 함수형 프로그래밍스파르탄스터디 E04 Javascript 객체지향, 함수형 프로그래밍
스파르탄스터디 E04 Javascript 객체지향, 함수형 프로그래밍
 
PySpark 배우기 Ch 06. ML 패키지 소개하기
PySpark 배우기 Ch 06. ML 패키지 소개하기PySpark 배우기 Ch 06. ML 패키지 소개하기
PySpark 배우기 Ch 06. ML 패키지 소개하기
 
일단 시작하는 코틀린
일단 시작하는 코틀린일단 시작하는 코틀린
일단 시작하는 코틀린
 
C++ Advanced 강의 4주차
 C++ Advanced 강의 4주차 C++ Advanced 강의 4주차
C++ Advanced 강의 4주차
 

스위프트 성능 이해하기

  • 1. let swift( 스위프트 성능 이해하기 Value 타입, Protocol과 스위프트의 성능 최적화 @inkyfox유용하
  • 2. 내용 Value Semantics 성능을 위해 고려할 것들 스위프트의 추상화 기법들과 성능
  • 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 변수를 말하는 것이 아님)
  • 15. Equatable 간단 구현 protocol Equatable { func ==(lhs: Self, rhs: Self) -> Bool }
  • 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. 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. 그래도.. 값 모두를 Copy하는데 성능 괜찮을까?
  • 19. Copy는 빠르다 기본 타입들, enum, tuple, struct • 정해진 시간 (constant time) 안에 끝남 내부 데이터가 Heap과 혼용하는 struct의 경우 • 정해진 시간 + 레퍼런스 copy등의 시간 • String, Array, Set, Dictionary 등 • 쓰기 시 Copy-on-write로 속도 저하 보완
  • 21. Immutable 참조형이어도 값이 불변하면 Thread간에 문제생길 일이 없음 함수형 패러다임과 같이 널리 전파됨 Immutable은 cocoa에서도 꽤 써 왔다 • NSArray *array = [array arrayByAddingObject: component]; • NSURL *url = [url URLByAppendingPathComponent: component];
  • 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내의 간접 저장소 역할) • 뒤에서 설명
  • 29. let swift( 성능을 위해 고려할 것들
  • 30. 성능에 영향을 미치는 3가지 Memory Allocation: Stack or Heap Reference Counting: No or Yes Method dispatch: Static or Dynamic
  • 31. Heap 할당의 문제 할당시에 빈 곳을 찾고 관리하는 것은 복잡한 과정 Heap
  • 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)
  • 65. Whole Module Optimization 빌드시에 모든 파일을 한번에 분석하여, static dispatch로 변환 가능한지 등을 판단하여 최적화
  • 66. Whole Module Optimization 빌드시에 모든 파일을 한번에 분석하여, static dispatch로 변환 가능한지 등을 판단하여 최적화
  • 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
  • 70. 추상화 기법들 Class Struct Protocol Type Generics Type 각각 앞서 소개한 성능 요소들에 대해 어떤 특징을 가지는가
  • 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, 상속, … • 단 레퍼런스의 의도하치 공유로 인한 문제 조심
  • 74. final class Memory Allocation: Heap Reference Counting: Yes Method Dispatch: Static
  • 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 …
  • 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. 참조 타입을 가진 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. 참조 타입을 가진 struct Memory Allocation: Stack Reference Counting: Yes Method Dispatch: Static
  • 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
  • 99. Existential Container Value Buffer (3 words) Protocol type의 실제 값을 넣고 관리하는 구조 (1 word는 32bit CPU에서는 32bit, 64bit CPU에서는 64bit) (Fixed size)
  • 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. 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. 어떻게 3 word를 구분해 할당하고 복사하는가?
  • 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
  • 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
  • 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. 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. 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. 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. 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
  • 136. Generics Type protocol Drawable { func draw() } func drawACopy<T: Drawable>(local: T) { local.draw() } drawACopy(Point(…)) drawACopy(Line(…))
  • 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. 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. 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. 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 특수화)
  • 144. 이걸 손으로 하면, Generics 쓰지 말란 말?
  • 145. Generic 특수화 (Specialization) 컴파일러가 해 줍니다. 더 효과를 보려면 WMO (Whole Module Optimization) 이용 아직 너무 믿진 마세요. (Xcode 7)
  • 146. Generics Type 정리 정적 다형성 (Static Polymorphism) • 컴파일 시점에 부르는 곳마다 타입이 정해져 있음 • 런타임에 바뀌지 않음 • 특수화 (Specialization)가 가능
  • 147. 특수화 되지 않은 Generics (작은 사이즈의 Protocol Type) Memory Allocation: Stack Reference Counting: No Method Dispatch: Dynamic (Protocol Witness Table)
  • 148. 특수화된 Generics Type (struct) Memory Allocation: Stack Reference Counting: No Method Dispatch: Static
  • 149. 특수화 되지 않은 Generics (큰 사이즈의 Protocol Type) Memory Allocation: MANY! (Copy할 때마다 할당) Reference Counting: No (class 프로퍼티가 있을 때만) Method Dispatch: Dynamic (Protocol Witness Table)
  • 150. 특수화된 Generics Type (class) Memory Allocation: Heap Reference Counting: Yes Method Dispatch: Dynamic (V-Table)
  • 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