Understanding Swift Performance - wwdc

Groot·2022년 8월 24일
0

TIL

목록 보기
45/148
post-thumbnail

TIL

🌱 난 오늘 무엇을 공부했을까?

📌 Understanding Swift Performance

📍 Allocation

  • 스택 할당은 매우 빠름.
  • 힙은 동적이지만 덜 효율적(O(log n)).
  • 또한 오버헤드가 많은 스레드 안전성을 확인하여 무결성을 보호해야 함.
struct Point {
  var x, y: Double
  func draw() { ... }
}

let point1 = Point(x: 0, y: 0)
var point2 = point1

// Since `Point` is a struct, this creates a copy. Modifying `point1` does not change `point2`.
point2.x = 5
  • 구조체 point1과 point2는 값타입이고 복사하기 때문에 서로 다름.
class Point {
  var x, y: Double
  func draw() { ... }
}

let point1 = Point(x: 0, y: 0)
var point2 = point1

// Since `Point` is a class, this adds a new reference to the same block of memory on the heap.
// The value of `x` is `5` for both references after this assignment.
point2.x = 5
  • 클래스로 만들면 참조 타입이라 복사를 하지 않기 때문에 값이 같다.
enum Color {
  case blue, green, gray
}

enum Orientation {
  case left, right
}

enum Tail {
  case none, tail, bubble
}

// Never construct something more than once; if you need it again, it’s already here.
var cache = [String: UIImage]()

func makeBalloon(_ color: Color, orientation: Orientation, tail: Tail) -> UIImage {
  // Add
  let key = “\(color):\(orientation):\(tail)if let image = cache[key] {
    return image
  }

  // If not cached, make a new balloon...
}
  • 문자열은 표현할 수 있는 값의 측면에서 거의 무한함. 그렇기 때문에 강력한 키가 아님.
  • 문자열은 문자의 내용을 힙에 간접적으로 저장. 캐시 히트가 발생하더라도 키를 구성할 때 여전히 힙 할당이 발생.
  • 키를 3가지 속성의 구조체로 나타내기. 이것은 일급 유형이며 사전에서 키로 사용이 가능하다.
  • 훨씬 안전하고 빠릅니다.
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 = [Attribute: 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
  }

  // If not cached, make a new balloon...
}

📍 ARC

  • 언제 힙에서 인스턴스를 할당 해제하는 것이 안전한지 알기 위해 Swift는 참조 카운팅을 사용. 아무도 가리키지 않으면 Swift가 할당을 해제.
  • 간접 및 스레드 안전성 검사가 많이 있고 이를 원자적으로 수행해야 하기 때문에 비용이 추가되기 때문에 ARC는 단순히 증가/감소하는 것보다 비용이 많이 든다.
class Point {
  var refCount: Int
  var x, y: Double
  func draw() { ... }
}

// Initialization assigns `refCount` to `1`.
let point1 = Point(x: 0, y: 0)

var point2 = point1
retain(point2)  // Increment `refCount`.
point2.x = 5

// Use `point1`.
release(point1)  // Decrement `refCount`.

// Use `point2`.
release(point2)
  • 구조체는 참조 계산 오버헤드가 없지만, 더 복잡한 구조체를 고려해 보자.
struct Label {
  var text: String  // Remember, `String` stores its characters on the heap.
  var font: UIFont  // `UIFont` is a class. This is also tracked on the heap.
  func draw() { ... }
}

// `Label` is declared on the stack, but its attributes have 2 pointers to the heap.
let label1 = Label(text: "hi", font: font)

// Since structs are value types and are copied, this means there are 2 *more* pointers
// to the same String and UIFont memory on the heap, causing 2x the overhead!
let label2 = label1
struct Label {
  var text: String
  var font: UIFont
  func draw() { ... }
}

let label1 = Label(text: "hi", font: font)
let label2 = label1

retain(label2.text._storage)
retain(label2.font)

// Use `label1`...

release(label1.text._storage)
release(label1.font)

// Use `label2`...

release(label2.text._storage)
release(label2.font)
  • 구조체는 프로퍼티로 0 또는 1개의 참조 유형이 있는 경우에만 ARC 측면에서 유리ㅈ함.
  • 이를 위해 uuid: String 대신 Foundation의 내장 UUID 유형을 사용하십시오.
  • 문자열 대신 열거형을 사용.

📍 Method Dispatch

  • Swift가 컴파일 시간에 실행할 메소드를 말할 수 있다면 이를 정적 디스패치라고 하며 런타임에 해당 코드로 바로 이동할 수 있습니다.
  • 컴파일러는 인라인(호출이 있는 위치에 함수 본문을 문자 그대로 복사하여 붙여넣기) 또는 기타 트릭을 통해 주변 코드에 적극적으로 최적화할 수도 있습니다.
  • 이것은 새로운 스택 프레임 등을 피하는 데 도움이 됩니다.
  • 정적 디스패치 체인을 실제로 원하는 코드로 축소할 수 있습니다.
  • 그렇지 않으면 코드에 도달하기 전에 호출할 메서드를 파악하는 오버헤드가 있는 동적 디스패치를 사용하고 주요 단점인 컴파일러 최적화를 포기합니다.
  • 상속 기반 다형성은 코드에서 더 많은 유연성을 제공하지만 정적 디스패칭을 포기.

참고

profile
I Am Groot

0개의 댓글