[WWDC 16 / Swift] Understanding Swift Performance with Method Dispatch

박준혁 - Niro·2024년 5월 8일
1

WWDC

목록 보기
11/11
post-thumbnail

안녕하세요 Niro 🚗 입니다!

갈수록 Swift 를 깊게 알아야겠다는 생각이 들어 WWDC 16 에서 소개된
🔗 Understanding Swift Performance 를 보며 공부하고 정리를 해보고자 적게 되었습니다.

해당 영상에서 중심적으로 얘기하는 3가지의 주제가 있습니다.

  1. 인스턴스가 생성될 때 Stack 과 Heap 중 어디에 할당되는가?
  2. 인스턴스를 할당할 때 Reference Counting 이 발생하는가?
  3. 인스턴스에서 Method 를 호출할때 정적 or 동적 Dispatch 가 되는가?

이 중 3번째 편으로 '인스턴스에서 Method 를 호출할때 정적 or 동적 Dispatch 가 되는가?' 에 대해 알아보고자 합니다.

🔗 인스턴스가 생성될 때 Stack 과 Heap 중 어디에 할당되는가?
🔗 인스턴스를 할당할 때 Reference Counting 이 발생하는가?

두 글을 읽고 오시면 더욱 이해가 잘 될거라 생각합니다.


1. 들어가기 앞서서

우리가 실행하는 앱에서 어떠한 메서드를 호출할 것인지 결정되는 시점에 따라 static mdethod dispatchdynamic method dispatch 2가지로 나눠진다고 합니다.

1-1. static mdethod dispatch

static mdethod dispatch 는 컴파일 시점에 실행할 메서드를 결정하게 됩니다.

즉, 컴파일러가 실행할 메서드가 어디있는지 파악할 수 있기 때문에 런타임 시기에서 메서드를 찾는 것이 아니라 바로 실행할 수 있게 되는 것이죠!

결과적으로 메서드 인라이닝과 같은 최적화도 가능하다고 합니다!

Methods Inlining 이란?

함수 호출로 인한 오버헤드를 제거하기 위해 사용합니다. 함수 호출은 함수의 본문으로 이동하고, 호출되는 함수의 상태를 저장하고 복원하는데 시간이 소요됩니다. 이러한 오버헤드는 함수가 짧거나 호출빈도가 높을 때 더 두드러지게 나타납니다.

직접 해당 함수로 이동하지 않고 호출된 함수의 본문을 호출한 위치에 직접 삽입함으로써 이러한 오버헤드를 줄입니다. 이렇게 하면 함수 호출이 발생하지 않으며, 코드가 실행될 때 해당 함수의 본문이 직접 실행되어 성능을 향상시킵니다.

즉, 최적화를 통해 함수 호출로 인한 오버헤드를 줄이고 코드 실행 속도를 향상시킵니다.

1-2. dynamic method dispatch

dynamic method dispatch 는 컴파일 시점에 어떤 메서드를 호출하는지 결정할 수가 없습니다. 컴파일 시점이 아닌 런타임 시점에서 Table 에 구현된 곳을 직접 찾아 이동해야만 합니다.

훨씬 더 많은 비용을 지불하겠구나 라고 생각했지만 많은 비용을 필요로 하지 않는다고 합니다. 단지 한 단계의 간접 참조만 있을 뿐 reference counting 와 Heap 할당 등 쓰레드 동기화 오버헤드가 없다고 합니다.

하지만 위에서 봤던 Methods Inlining 최적화 기술은 dynamic method dispatch 에서 쓸 수 없다고 하네요..

그 이유를 알아보고자 합니다.


2. Struct 에서의 Method Dispatch

1,2 편에서 봤던 익숙한 예시를 다시 들고 왔습니다.

Point 구조체는 draw 메서드를 갖고 있습니다. drawAPoint 메서드는 Point 유형을 파라미터로 받아 인스턴스의 draw 메서드를 호출하게 됩니다.

여기서는 x, y 를 0 으로 인스턴스화 시킨 Point 객체를 drawAPoint 메서드 파라미터로 전달하고 있습니다.

뭔가 느낌이 오시나요?
drawAPoint 메서드와 point.draw 메서드는 모두 컴파일러가 실행된 메서드를 정확히 알고 있기 때문에 static mdethod dispatch 이라 볼 수 있습니다.

즉, drawAPoint 메서드가 실행되면 해당 구현부를 갖고오고 param.draw 메서드가 실행되면 해당 구현부를 가져오게 될겁니다.

런타임 시점에서는 point 를 만들어 구현부를 실행시키고 끝나는 것이죠!

결과적으로 두 메서드를 처리하는 과정에서 함수 호출에 대한 별다른 오버헤드가 발생하지 않습니다. Method Inlining 덕분에 메서드를 호출할 필요가 없었기 때문죠!

항상 고민인게 각자 한개씩 있다면 그렇게 차이가 날까 싶지만 그렇게 큰 차이는 아니라고 합니다. 하지만 여러개의 method dispatch 가 발생한다면 큰 차이가 발생하겠죠?

static mdethod dispatch 에서는 모든 단계를 파악할 수 있지만 dynamic dispatch 는 컴파일러가 추론을 할 수 없습니다....

모든 단계를 파악할 수 있는 static mdethod dispatch 특징으로 인해 Stack 오버헤드를 발생시키지 않고 method Inlining 을 통해 하나로 압축시켜 단일 구현이 가능하게 됩니다.


3. Class 에서의 Method Dispatch

움.. 그러면 성능이 더 느린 dynamic dispatch 가 왜 필요할까요?

가장 큰 이유는 다형성 때문이라는데.. 전통적인 객체지향 프로그래램을 살펴보면서 이유를 찾아보겠습니다.

Drawable 클래스가 보이는데 추상화 되어있는 부모 클래스이고 Point, Line 클래스는 Drawable
의 자식 클래스 입니다.

각 자식 클래스들은 draw 메서드를 override 하여 각자 구현부를 작성하고 Drawable 유형의 배열을 만들었습니다.

해당 배열에는 주소값이 저장되기 때문에 모두 크기가 같은 특징을 갖고 있고 Line 클래스가 포함될 수도, Point 클래스가 포함될 수도 있는 상황에서 반복문을 통해 draw 메서드를 호출할 겁니다.

바로 이것이 다형성의 특징이죠!

그러면 해당 동작은 어떻게 진행이 될까요?

무언가 복잡해보이는데 감이 잡히시나요?

컴파일 시점에서 draw 메서드에 대해 Pointdraw 를 호출할 것인지, Linedraw 를 호출할지 정확하게 알지 못한다는 것입니다.

이 문제를 해결하기 위해 컴파일러는 Class 에 필드 하나를 추가하게 됩니다.

해당 필드는 Class 의 유형 정보를 가리키는 포인터이며 static memory 에 저장된다고 합니다.

즉, 우리는 draw 메서드를 호출할 때 컴파일러는 Static memory 에 저장된 해당 유형의 virtial method table 을 통해 메서드를 호출하게 되는 것이죠

갑자기 이상한.. table 이 튀어나왔죠..?

virtial method table 을 다른 말로 V-Table 이라고도 하고 실제로 컴파일러가 해당하는 유형의 메서드를 호출하는지 어떤 작업을 해야하는지 다음과 같이 표현이 가능하다고 합니다.

d 변수의 typevirtial method table 을 통해 올바른 메서드를 찾게 되고 실제 인스턴스를 암묵적 self-parameter 로 넘기는 것을 볼 수 있습니다.

위의 이미지 에서는 Line 이라는 클래스의 draw 메서드를 호출할 수 있도록 되어있네요!


4. 정리해보자면

마지막 세번째 편인 '인스턴스에서 Method 를 호출할때 정적 or 동적 Dispatch 가 되는가?' 에 대해 알아보았습니다.

매우 매우 매우 어려운 내용들이지만 우리는 굉장히 중요한 것을 얻은거 같습니다.

1편에서부터 3편까지의 내용을 다시 상기시키면서 끝내보고자 합니다.

Class 는 Heap 영역에 할당하게 되고 reference counting 을 통해 참조되고 있지 않다면 메모리에서 해제하도록 효율적으로 관리하고 있습니다. 또한 dynamic dispatch 를 통해서 적절한 메서드를 호출하게 되죠

물론.. 메서드 체이닝과 같이 여러 메서드의 호출이 발생한다면 Method inlining 과 같은 최적화 기술을 사용할 수가 없습니다..

우리는 앞서 자식 클래스를 만들고 V-Table 을 통해 해당 유형의 메서드를 호출하는 동작 방식을 알게 되었습니다.

Class 는 컴파일 시점이 아닌 런타임 시점에서 호출을 하게 되는건데 모든 Class 에서 dynamic dispatch 가 필요한건 아니겠죠..?

만약 상속이 없는 상황인, 자식 클래스를 만들지 않는 Class 일 경우 Class 명 앞에 final 키워드를 선언해준다면 컴파일러는 해당 Class 는 자식 클래스가 없으니 static 으로 dispatch 하도록 전환하게 됩니다.

또한 팀원들이게 해당 Class 는 상속하지 않아요~ 라고 의도가 담겨있기도 합니다!


4-1. 영상에서 강조한 부분!

자, WWDC 영상에서 강조하는건 우리 모두 스스로에게 물어볼 질문들을 기억해야한다고 합니다.

Swift 코드를 읽고 쓸때마다

  1. 해당 인스턴스는 Stack 또는 Heap 에 할당 될 것인가?
  2. 해당 인스턴스를 전달할 때 reference counting 오버헤드가 발생할 것인가?
  3. 해당 인스턴스의 메서드를 호출할 때 static 하게, 또는 dynamic 하게 Dispatch 할 것이냐?

라는 질문을 끊임 없이 자신에게 해야한다고 합니다!

사실 이런 생각없이 단순히 주어진 업무를 하기 마련이죠..
개발자로써 너무나도 좋은 소양 같습니다.

앞으로 꼭 이런 생각과 함께 코드를 작성할 수 있도록 해야겠네요!

3편으로 나누어 정리해봤는데 WWDC 영상을 본다는건 시간이 오래걸리고... 정리하는 것도 오래걸리지만..

그래도 다른 사람의 글을 읽는 것보다 더 기억이 남아 더욱 좋은거 같습니다.

긴글 읽어주셔서 감사하고 피드백은 환영입니다!

profile
📱iOS Developer, 🍎 Apple Developer Academy @ POSTECH 1st, 💻 DO SOPT 33th iOS Part

0개의 댓글

관련 채용 정보