@ WWDC 16
์ด๋ฒ ์์์ ๋ชฉํ๋ ์ฑ๋ฅ์ ์ดํดํ๊ธฐ ์ํด์ ๊ตฌํ์ ์ดํดํ๋ ๊ฒ!
Understand the implementation to understand performance
- Allocation
- Reference counting
- Method dispatch
- Protocol types
- Generic code
- Decrement stack pointer to allocate
- Increment stack pointer to deallocate
์คํ์ ์ฑ์ง์ ์๊ฐํด๋ณด๋ฉด ์ฝ๊ฒ ์ดํด ๊ฐ๋ฅ. push, pop! ์คํ์ ๊ต์ฅํ ๊ฐ๋จํ ์๋ฃ๊ตฌ์กฐ์ด๊ณ stack allocation์ ๊ต์ฅํ ๋น ๋ฅด๋ค.
- Advanced data structure
- Search for unused block of memory to allocate
- Reinsert block of memory to deallocate
- Thread safety overhead
ํ์ ๋์ ์ธ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ฐ๊ฒ ํ๋ ๊ฒ๊ณผ ๊ฐ์ด ์คํ์ด ํ์ง ๋ชปํ๋ ๊ฒ์ ํ ์ ์๊ฒ ํด์ค๋ค. ๋์ ๋ ๋ณต์กํ ์๋ฃ๊ตฌ์กฐ๋ฅผ ๊ฐ๋๋ค. ํ์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํ๊ณ ์ ํ๋ฉด, ํ ์๋ฃ๊ตฌ์กฐ์์ ์ ๋นํ ์ฌ์ด์ฆ์ ์ฌ์ฉํ ์ ์๋ ๋ธ๋ฝ์ ์ฐพ์์ผ ํ๋ค. deallocateํ ๋๋ ํด๋น ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ ์ ํ ์์น์ ๋ค์ ๋ฃ์ด์ค์ผ ํ๋ค. ์ด์ฒ๋ผ heap allocation์๋ stack allocation์ด ๊ฐ๋จํ๋ ๊ฒ๊ณผ๋ ๋ฌ๋ฆฌ ํ ๋น๊ณผ ๊ด๋ จ๋ ๋น์ฉ์ด ๋ ๋ค. ์ฌ๋ฌ ์ค๋ ๋์์ ๋์์ ํ์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํ๋ ค๊ณ ํ ์ ์๊ธฐ ๋๋ฌธ์ ํ์ lock์ด๋ ๋ค๋ฅธ ๋๊ธฐํ ๋ฉ์ปค๋์ฆ์ ์ด์ฉํด์ผ ํ๊ณ ์ด๋ ์๋นํ ์ค๋ฒํค๋๋ค.
์ด ํจ์์ ๋ค์ด๊ฐ์ ์ด๋ค ์ฝ๋๋ ์คํํ๊ธฐ ์ ์ ์ด๋ฏธ stack ์์ญ์ point1๊ณผ point2๋ฅผ ์ํ ๊ณต๊ฐ์ด ํ ๋น๋๋ค. point1์ด ์๋ ๋ผ์ธ์ ์คํํ๋ฉด ์ด๋ฏธ ํ ๋น๋์ด ์๋ point1์ด ์ด๊ธฐํ๋๊ณ , point2๋ point1์ ๋ณต์ฌํด์ ์ด๊ธฐํ๋๋ค. point2.x = 5๋ฅผ ์คํํ๋ฉด ๋จ์ํ point2์ x๋ง์ ๋ฐ๊พผ๋ค. ์ด๋ฅผ value semantics๋ค! ํด์ ํ ๋ ์คํํฌ์ธํฐ๋ฅผ ์ฆ๊ฐ์์ผ์ ํด์ ํ๋ค.
ํด๋์ค๋ฅผ ์ด์ฉํ์ ๋๋ struct๋ฅผ ์ด์ฉํ์ ๋์ ๊ฐ์ด ํจ์์ ๋ค์ด๊ฐ๋ฉด ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์คํ์ ํ ๋นํ๋ค. ์ด ๋ ์ค์ ์ ์ธ Point์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํ๋ ๊ฒ์ด ์๋๋ผ point1๊ณผ point2์ ๋ ํผ๋ฐ์ค๋ฅผ ํ ๋นํ๋ค. (0, 0)์ ์์ฑํ ๋ ์ค์ํํธ๋ ํ์ lockํ๊ณ ์๋ฃ ๊ตฌ์กฐ์์ ์ฌ์ฉ๋์ง ์์ ์ ์ ํ ํฌ๊ธฐ์ ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ฝ์ ์ฐพ๋๋ค. ๊ทธ๋ฆฌ๊ณ point1์ ๋ ํผ๋ฐ์ค๋ฅผ ํ ์์ญ์ ์๋ ๋ฉ๋ชจ๋ฆฌ์ ์ฃผ์๋ก ์ด๊ธฐํํ๋ค. struct๋ฅผ ์ฌ์ฉํ ๋๋ ์ค์ ํฌ๊ธฐ๋๋ก ๋ฉ๋ชจ๋ฆฌ์ ํ ๋นํ๊ธฐ ๋๋ฌธ์ ํฌ์ธํธ๋ฅผ ํ ๋นํ ๋ 2 ์๋๋ง ์ฌ์ฉํ์ง๋ง ํด๋์ค๋ฅผ ์ด์ฉํ ๋๋ 4 ์๋๊ฐ ํ ๋น๋๋ค. ๊ด๋ฆฌ๋ฅผ ์ํด์ 2 ์๋๊ฐ ๋ ํ ๋น๋ ๊ฒ.
point1์ด ์ด๊ธฐํ๋๊ณ ๋ ๋ค์ let point2 = point1 ์ ์คํํ๋ฉด point2๋ point1์ ๋ ํผ๋ฐ์ค๋ฅผ ๋ณต์ฌํ๋ค. ๊ทธ๋์ point2์ point1์ด ์์ ํ ๊ฐ์ ์ธ์คํด์ค๋ฅผ ์ฐธ์กฐํ๊ฒ ๋๋ค. ์ด๊ฒ์ด reference semantics๋ค! ๊ทธ๋ฆฌ๊ณ reference semantics๋ ์๋ํ์ง ์๊ฒ state๋ฅผ ๊ณต์ ํ๊ฒ ํ ์๋ ์๋ค. ํด์ ํ ๋ ์ค์ํํธ๊ฐ ํ์ lock์ ๊ฑธ๊ณ ๋ฉ๋ชจ๋ฆฌ๋ฅผ deallocateํ ๋ค์ ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ฝ์ ๋ค์ ์ ์ ํ ์์น๋ก ๋๋ ค๋๋๋ค. ๊ทธ ๋ค์ ์คํ์ popํ๋ค.
// Modeling Techniques: Allocation
enum Color { case blue, green, gray }
enum Orientation { case left, right }
enum Tail { case none, tail, bubble }
var cache = [String : UIImage]()
func makeBalloon(_ color: Color, orientation: Orientation, tail: Tail) -> UIImage {
let key = "\(color):\(orientation):\(tail)"
if let image = cache[key] {
return image
}
...
}
์์ ๊ฐ์ด caching์ ํ๋ ์ฝ๋๊ฐ ์๋ค๊ณ ํ์! String์ ์ด์ฉํด์ key๋ฅผ ๋ง๋๋ ๊ฒ์ ๊ทธ๋ ๊ฒ ์์ ํ ๋ฐฉ๋ฒ์ด ์๋๊ณ , String์ Character๋ฅผ ์ ์ฅํ ๋ ๊ฐ์ ์ ์ผ๋ก heap allocation์ ์ฌ์ฉํ๋ค.
๊ทธ๋์ ์ด๋ฐ ๋ฐฉ์์ ์ถ์ฒํฉ๋๋ค!!
// Modeling Techniques: Allocation
enum Color { case blue, green, gray }
enum Orientation { case left, right }
enum Tail { case none, tail, bubble }
struct Attributes: Hashable {
var color: Color
var orientation: Orientation
var tail: Tail
}
var cache = [Attributes : UIImage]()
func makeBalloon(_ color: Color, orientation: Orientation, tail: Tail) -> UIImage {
let key = Attributes(color: color, orientation: orientation, tail: tail)
if let image = cache[key] {
return image
}
...
}
String์ ์ฌ์ฉํ ๋๋ณด๋ค ๋ ์์ ํ๊ณ , ๊ทธ ์ด๋ค heap allocation๋ ์๋ค!
- There's more to reference counting than incrementing, decrementing
- Indirection
- Thread safety overhead
์๊น ๋ณด์๋ Point ์ฝ๋์ reference counting์ ๋ณด๋ฉด ์ด๋ฐ ๋๋์ด๋ค.
point1์ด ์ด๊ธฐํ๋ ๋ refCount: 1, point2์ ๋ณต์ฌํ๊ณ retain๋์ refCount: 2 ์ดํ release๋๋ฉด์ refCount: 1, refCount: 0์ด ๋๊ณ refCount๊ฐ 0์ด ๋๋ฉด deallocate๋๋ค. struct๋ reference๋ฅผ ํ์ง ์๊ธฐ ๋๋ฌธ์ reference counting์ ํ์ง ์๋๋ค.
์ด๋ฐ ๊ฒฝ์ฐ์๋ ์ค๋ฒํค๋๊ฐ ๋ง์ด ์ผ์ด๋๋ค! struct๋ฅผ ์ฌ์ฉํ๊ณ ์๊ธด ํ์ง๋ง ๋ด๋ถ์ ์ผ๋ก heap allocation์ด ์ผ์ด๋๊ณ ์๊ธฐ ๋๋ฌธ์ reference counting๋ ๋ง์ด ์ผ์ด๋จ.
์ด๋ฐ ๊ตฌ์กฐ๋ ์ด๋ป๊ฒ ๊ฐ์ ์ํฌ ์ ์์๊น? String์ ์์ ๋งํ๋ค์ํผ struct์ด์ง๋ง ๋ด๋ถ Character๋ค์ด heap์ ํ ๋น๋๊ธฐ ๋๋ฌธ์ reference counting์ด ๋ง์ด ์ผ์ด๋๋ค.
// Modeling Techniques: Reference Counting
enum MimeType: String {
case jpeg = "image/jpeg"
case png = "image/png"
case gif = "image/gif"
}
struct Attachment {
let fileURL: URL
let uuid: UUID
let mimeType: MimeType
init?(fileURL: URL, uuid: UUID, mimeType: String) {
guard let mimeType = MimeType(rawValue: mimeType) else { return nil }
self.fileURL = fileURL
self.uuid = uuid
self.mimeType = mimeType
}
}
uuid์๋ UUID ํ์ ์ ์ด์ฉํ๊ณ , String ํ์ ์ด์๋ mimeType์ enum์ ์๋ก ๋ง๋ค์ด์ ์ด์ฉํ๋ค. ์ด๋ ๊ฒ ํจ์ผ๋ก์จ ์ข ๋ ์์ ํ๊ณ ๋ ์ ์ reference counting์ ํ๋ค! (์ฑ๋ฅ์ด ๋ ์ข์์ง๋ค๋ ๋ง์ด๊ฒ ์ฃ ~!)
- Jump directly to implementation at run time
- Candidate for inlining and other optimizations
- no call stack overhead!
- Look up implementation in table at run time
- Then jump to implementation
- Prevents inlining and other optimizations
static dispatch์ dynamic dispatch ์ฌ์ด์๋ ์์ฒญ๋ ์ฑ๋ฅ ์ฐจ์ด๊ฐ ์๋ ๊ฒ์ ์๋๊ณ , ๊ฐ์ ์ฑ ๋ ๋ฒจ์ด ํ๋ ์ถ๊ฐ๋๋ค. dynamic dispatch์๋ ์ค๋ ๋ ๋๊ธฐํ ์ค๋ฒํค๋์ฒ๋ผ reference counting์ด๋ heap allocation์์ ์์๋ ์ค๋ฒํค๋๊ฐ ์๋ ๊ฒ์ ์๋๋ค. ํ์ง๋ง ์ปดํ์ผ ํ์์์ optimization์ด ์ ๋จ ใ ์ด๊ฒ ๊ต์ฅํ ์ค์ํ ํฌ์ธํธ! ํ๋์ static dispatch์ ํ๋์ dynamic dispatch ์ฌ์ด์๋ ํฐ ์ฐจ์ด๊ฐ ์์ง๋ง chainingํ ๊ฒฝ์ฐ์๋ ์ด optimization ๋๋ถ์ ๋ง์ ์ฑ๋ฅ ์ฐจ์ด๊ฐ ์๋ค.
subclassingํ์ง ์์ class์ ๋ํด์๋ final ํค์๋๋ฅผ ๋ถ์ฌ์ฃผ์! ์ด๋ ๊ฒ ํ๋ฉด dynamic dispatch๋ฅผ static dispatch๋ก ๋ฐ๊ฟ ๊ฒ์ด๋ค.
๊ทธ๋ผ dynamic dispatch๋ ์ ์ธ๊น?! polymorphism ๋๋ฌธ์ด๋ค.
polymorphism์ inheritance๋ reference semantics ์์ด ์ฌ์ฉํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์๋ค. Protocol์ ์ด์ฉํ๋ ๊ฒ์ด๋ค. ์ด๋ป๊ฒ ์ด๊ฒ์ด ๊ฐ๋ฅํ๋๋! Existential Container๋ฅผ ์ด์ฉํ๊ธฐ ๋๋ฌธ.
- Inline Value Buffer: currently 3 words
- Large values stored on heap
- Reference to Value Witness Table (allocation, copy, destruction)
- Reeference to Protocol Witness Table
์๋ํ๋ ์์๋ ์๋์ ์๋ Generated code๋ฅผ ๋ณด๋ฉด ๋๋ค.
ํ๋กํ ์ฝ ํ์ ์ existential container๋ก ํํ๋๊ณ , ํฌ๊ธฐ๊ฐ ์์ ๊ฐ๋ค์ value buffer์, ํฌ๊ธฐ๊ฐ ํฐ ๊ฐ์ ๋ ํผ๋ฐ์ค๋ก ์ ์ฅํ๋ค.
์ด๋ฐ ๊ฐ๋ค์ ๋ณต์ฌํ๊ฒ ๋๋ฉด copy-on-write ๋ฐฉ์์ ์ฌ์ฉํ๊ฒ ๋๋ค. first์ second๊ฐ ๊ฐ์ ๋ ํผ๋ฐ์ค๋ฅผ ๊ฐ๋ฆฌํค๊ณ ์๋ค๊ฐ second.x1 = 3.0์ผ๋ก ๊ฐ์ ๋ณ๊ฒฝํ๊ฒ ๋๋ฉด
๊ทธ๋ ์๋ก์ด ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํด์ ๋ณต์ฌํ๋ค.
class LineStorage { var x1, y1, x2, y2: Double }
struct Line: Drawable {
var storage: LineStorage
init() { storage = LineStorage(Point(), Point()) }
func draw() { ... }
mutating func move() {
if !isUniquelyReferencedNoneObjc(&storage) { // uniqueํ๊ฒ ์ฐธ์กฐํ๊ณ ์๋ ๊ฒ ์๋ ๊ฒฝ์ฐ์๋ ์๋กญ๊ฒ ํ ๋นํ๋ค.
storage = LineStorage(storage)
}
storage.start = ...
}
}
- Fits in Value Buffer: no heap allocation
- No reference counting
- Dynamic dispatch through Protocol Witness Table (PWT)
- Heap allocation
- Reference counting if value contains references
- Dynamic polymorphism
- Indirection through Witness Tables and Existential Container
- Copying of large values causes heap allocation
- Static polymorphism
- One type per call context
- Type substituted down the call chain
- One shared implementation
- Uses Protocol/Value Witness Table
- One type per call context: passes tables
func drawACopy<T: Drawable>(local: T) {
local.draw()
}
drawACopy(Point(...))
drawACopy(Line(...))
- Value Buffeer: currently 3 words
- Small values stored inline
- Large values stored on heap
๊ทธ๋ผ ์ค์ ๋ก Generic์ ์ฌ์ฉํ๋ฉด ๋ ๋นจ๋ผ?
- Static polymorphism: uses type at call-site
- Creates type-specific version of method
- Version per type in use
- Can be more compact after optimization
whole module optimization์ ์ด์ฉํด์ ๋ ์ต์ ํ๋ฅผ ์ํฌ ์ ์๋ค. Xcode6๋ถํฐ ๊ธฐ๋ณธ!
struct Pair<T: Drawable> {
init(_ f: T, _ s: T) {
first = f
second = s
}
var first: T
var second: T
}
var pair = Pair(Line(), Line())
- Type does not change at runtime
- Storage inline!!!!
- Performance characteristics like struct types
- No heap allocation on copying
- No reference counting
- Static method dispatching
- Performance characteristics like class types
- Heap allocation on creating an instance
- Reference counting
- Dynamic method dispatch through V-Table
- No heap allocation: value fits in Value Buffer
- No reference counting
- Dynamic dispatch through Protocol Witness Table
- Heap allocation (use indirect storage as a workaround)
- Reference counting if value contains references
- Dynamic dispatch through Protocol Witness Table
- Choose fitting abstraction with the least dynamic runtime type requirements
- struct types: value semantics
- class type: identity or OOP style polymorphism
- Generics: static polymorphism
- Protocol types: dynamic polymorphism
- Use indirect storage to deal with large values