Swift의 다형성과 성능

Wonbi·2024년 4월 2일
0

Swift 뿌수기

목록 보기
12/12

💎 다형성과 성능

  • 이전에 Swift에서 다형성을 구현하는 방법으로 Protocol을 활용하고, 연관값과 some, any키워드에 대해 알아보았다.
  • 근데 any 키워드에 대해 쭉 읽다보면 뭔가 성능적으로 구려질거 같은데.. 라는 생각이 들었는데 그게 맞았다.
  • 다형성 구현을 위해선 어쩔 수 없이 동적 디스패치를 사용하게 되는데, 오늘은 이에 대해 좀 더 자세하게 알아보자.

✏️ Static Dispatch

  • 메서드를 재정의할 수 없는 경우 정적 디스패치가 수행된다.
  • 컴파일러가 명령(instructions)들이 어디있는지 알기 때문에, 함수가 호출되면 컴파일러는 수행할 함수의 메모리 주소로 바로(Directly) 점프한다. 이는 성능을 향상시키고 컴파일러가 inlining과 같은 최적화를 가능하게 한다.
  • 메모리 주소로 바로 넘어가는 정적 디스패치는 당연히 동적 디스패치보다 빠르다.
  • finalstatic 키워드를 사용하여 정적 디스패치 사용 가능하다.
  • 값 타입에 정의된 func는 재정의할 수 없기 때문에 값타입 메서드는 기본적으로 이 정적 디스패치를 활용한다. 이는 참조 타입보다 성능적으로 빠르다는 의미이다.

✏️ Dynamic Dispatch

  • 컴파일 타임이 아닌 런타임에 어떤 메소드의 구현을 고를지 결정한다.
  • 정적 디스패치에 비해 오버헤드가 더 들게 된다.
  • 다형성을 위해 대부분의 OOP 언어들은 동적 디스패치를 지원한다.

✏️ V-Table Dispatch

  • 이 디스패치는 테이블을 사용하는 기법이다.
  • 테이블이란 함수의 포인터로 이루어진 배열을 의미하며, Witness Table 혹은 Virtual Table(V-Table)이라 한다.
  • 이 테이블은 자신이 처리할 수 있는 모든 메서드에 대한 포인터를 유지하기 때문에, 특정 메서드의 구현을 찾는 용도로 사용된다. 메서드가 호출되면 이 테이블을 참조하여 실제 호출될 메서드를 이 테이블에서 찾는다.
  • 부모 타입으로부터 상속받은 메서드일 경우, 같은 주소값을 유지하고 재정의하게 되면 이를 덮어쓴다. 그리고 해당 메서드에 대한 함수의 포인터가 배열 끝에 추가된다.
  • 정적 디스패치와 달리 컴파일러가 먼저 테이블로부터 메모리 주소를 읽고, 해당위치로 점프해야 하므로 두번의 연산이 요구된다. 바로 점프하는 정적 디스패치보다 느릴 수밖에 없는이유. (그래도 메시지 디스패치 보다는 빠르다.)

📍 Message Dispatch

  • 메시지 디스패치는 자기 자신이 재정의하거나 새로 정의한 메서드들만 테이블이 유지하는 방식이다.
  • 대신, 부모 타입으로의 포인터를 가지고 있어서, 부모 타입의 메서드들은 부모 타입으로 이동하여 찾아 실행한다.
  • 이 동적 디스패치 기법은 가장 동적인 방식이다. 사실, 이 기법은 다형성과 OOP에 매우 적합해 Cocoa 프레임워크의 KVO, 코어데이터와 같은 곳에서 자주 사용 된다.
  • 이 디스패치 기법의 가장 큰 장점은 유연함이다. 장점은 곧 런타임에 메서드의 기능을 바꾸는 메서드 스위즐링(Method Swizzling)을 가능하게 한다.
  • 메서드 스위즐링은 기존의 메서드를 런타임 때 원하는 메서드로 바꾸어 사용할 수 있도록 하는 기법을 말한다.
  • 이러한 기능을 제공하는 런타임 라이브러리로 Swift는 Objective-C 런타임을 이용한다. 즉, Message Dispatch를 이용하기 위해서는 Objective-C 런타임에 의존해야 한다.
  • 메시지 디스패치는 Objective-C 런타임을 사용하기 때문에 메시지가 디스패치 될 때 런타임은 클래스 계층(hierarchy)을 모두 보고(crawl) 실행할 메소드를 결정하는데, 이 과정이 매우 느리기 때문에 성능향상을 위해 캐시를 제공하기도 한다.

0개의 댓글