Understand Swift Performance - Generic

고라니·2024년 2월 23일
0

TIL

목록 보기
62/67

이번에는 Understand Swift Performance의 마지막!
Generic을 활용하여 성능을 향상시키는 방법에 대해 알아보자

지금까지 Swift의 성능에 대해 알아본 주요 내용은 다음과 같다.

  • 스택: 후입선출(LIFO) 방식으로 단순한 방식으로 관리되어 빠른 메모리 할당과 해제가 가능하다.
  • 힙: 동적 메모리 할당에 사용되며, 더 복잡한 데이터 구조 공간이다.
  • 클래스: 상속이 가능, 다형성을 지원, 동적 디스패치와 힙 할당으로 인해 성능 저하가 발생할 수 있음.
  • 구조체: 상속은 불가, 정적 디스패치를 통해 성능 향상, 다형성은 직접적으로 지원하지 않음
  • 구조체 + 프로토콜: 구조체의 상속 불가한 한계를 극복, 다형성을 지원, 크기가 큰 경우 힙에 할당될 수 있으며 동적 디스패치가 적용 됨

다형성

다형성은 하나의 인터페이스나 메서드가 여러 형태로 동작하는 것을 의미한다. 이는 overriding과 overloadin을 통해 구현된다.(구조체의 경우 Protocol을 통해 다형성 지원)

  • overriding: 상속 관계에 있는 클래스 간에 메서드를 재정의 한다.

  • overloading: 같은 이름의 매서드를 매개변수의 타입, 개수, 순서 등을 다르게 하여 여러 번 정의한다.


제네릭코드의 다형성

Parametric Polymorphism이란?

제네릭을 사용하면 함수가 다양한 종류의 타입 매개변수에 대해 동일한 작업을 수행할 수 있다. 이는 매개변수화된 다형성을 의미하며 코드의 재사용성과 타입 안전성을 높인다.

Static한 Polymorphism이란?

정적 다형성은 컴파일 시점에 타입이 결정되는 다형성을 말한다. 제네릭을 사용하면 컴파일러는 제네릭 타입 매개변수를 실제 타입으로 대체하여 최적화된 코드를 생성한다. 이는 런타임에 성능을 향상시킨다.


활용

protocol Drawable {
    func draw()
}

// 1. 제네릭 사용 x
func drawACopy(local: Drawable) {
    local.draw()
}

// 2. 제네릭 사용 o
func drawACopy<T: Drawable>(local: T) {
    local.draw()
}

let line = line()
drawACopy(line)

let point = Point()
drawACopy(point)

제네릭을 사용하지 않는 겨우

local의 실제 타입을 컴파일 시점에 알 수 없기 때문에 런타임 중에 해당 타입의 draw() 메서드를 찾는 과정이 필요하다. 이는 동적 디스패치 방식으로 작동한다.

제네릭을 사용하는 경우

컴파일 시점에 매개변수 T를 실제 타입으로 대체한 코드를 생성한다. 이는 해당 타입의 draw()메서드를 정적 디스패치 방식으로 호출하게 하여 성능을 최적화 한다.


제네릭과 프로토콜의 한계와 부작용

제네릭과 프로토콜을 함께 사용하면 성능을 개선할 수 있지만 부작용도 있다.

  • 타입 제약의 복잡성: 무분별하게 사용하면 코드의 가독성을 저하시킬 수 있다.
  • 컴파일 시간 증가: 제네릭은 컴파일러가 타입을 추론하는 시간이 필요하기 때문에 컴파일 시간이 증가할 수 있다.
  • 디버깅: 제네릭 코드는 디버깅할 때 타입 정보가 추상화 되어 있기 때문에, 문제를 진달하기 어려울 수 있다.

마치면서

Understand Swift Performance의 마지막 글이다. 지금까지 개발자로서 Swift의 성능을 개선하는 방법을 배울 수 있었다. 유연성이 높으면 성능은 낮아지고 성능을 높이면 유연성이 낮아진다. 그렇기 때문에 항상 성능과 유연성 사이에 균형을 찾고 상황에 맞는 최적의 솔루션을 위해 항상 고민해야 한다.
이렇게 개념만 알고 넘어가지 말고 꼭 실제 프로젝트에 적용하도록 하겠다!

profile
🍎 무럭무럭

0개의 댓글