Static Dispatch && Dynamic Dispatch

2023. 2. 16. 09:35CS

반응형

컴파일

Swift 컴파일, 컴파일러

런타임

  • 런타임이란 프로그램이 실행되고 있는 동안의 동작을 의미한다.

런타임 오류

  • 런타임 오류란 런타임 시 발생한 오류에 대해서 의미한다.
  • 런타임 오류에 대해서 처리하도록 만든것이 예외처리이다.
let numbers = ["1", "2", "a"]

for num in numbers {
    print(Int(num)!)    // Thread 1: Fatal error: 
}
  • 강제 옵셔널 추출같은 경우에도 컴파일타임에서는 확인하지 못하고 이 코드가 실행하는 런타임에서 발생하므로 런타임 오류이다.
print(numbers[3])    // Thread 1: Fatal error: Index out of range
  • 위와 같이 배열의 범위를 벗어난 Index는 접근이 불가능하기때문에 런타임시에 에러가 발생하게 됩니다.
  • 위 예제들과 같이 컴파일 단계에서 확인할 수 없고, 코드가 실행되는 런타임동안 확인할 수 있는 에러를 런타임에러라고 합니다.

Dispatch

  • 디스패치란, 어떤 메소드를 호출할 것인가를 결정하여 그것을 실행하는 과정
  • 정적(Static)디스패치, 동적(Dynamic)디스패치가 있다.
  • 정적디스패치는 컴파일 시점, 동적디스패치는 런타일 시점에서 어떤 메소드를 호출할 것인지를 결정한다.

Static Dispatch

  • 컴파일 시점에서 어떤 메소드를 호출할지 결정해준다.
  • 컴파일시점에서 이에 대해 결정해주므로 런타임에 비해 실행시 성능상 이점을 가질 수 있다.
  • 기본적으로 값 타입에 있는 메소드들은 Static Dispatch를 사용한다.

Dynamic Dispatch

  • 런타임 시점에서 어떤 메소드를 호출할지 결정해준다.
  • 런타임시점에서 결정하기 때문에 컴파일 타임에 비해 실행시 성능이 좋지 않다.

왜 Static Dispatch에 비해 느린 Dynamic Dispatch를 사용하는 걸까?

class School {
    func eatLunch() {
    }
}

class HighSchool: School {
    override func eatLunch() {
        super.eatLunch()
    }
}

class ElementSchool: School {
    override func eatLunch() {
        super.eatLunch()
    }
}
  • eatLunch메소드를 사용한다면 이는 School, HighSchool, ElementSchool의 어떤 타입의 eatLunch메소드를 사용하는지에 대하여 해당 메소드를 저장해두었다가 해당 메소드를 부르고 타입을 확인하는 과정을 거쳐야한다.
  • 그러나, 상속가능한 객체는 메모리에 런타임에 할당되므로 런타임 시점에서만 확인할 수 있다.
  • 그러므로 위와 같이 override가능한 메소드의 경우 정확한 타입을 확인하기 위해 Dynamic Dispatch를 사용하게 된다.

어떤 타입인지 어떻게 구별하는 걸까?

let busanHighSchool = HighSchool()
let koreaElementSchool = ElementSchool()

busanHighSchool.eatLunch()
koreaElementSchool.eatLunch()
  • 위 코드를 통해 HighSchool타입의 인스턴스 eatLunch와 ElementSchool타입의 인스턴스 eatLunch를 사용한다는 것은 알 수 있다.
  • 다만, 컴파일타임에서는 인스턴스가 아직 메모리에 할당되지 않았으므로 어떤 하위 클래스의 인스턴스인지 알 수 없으므로 런타임 시점에 실행되게 된다.
  • Dispatch는 각각의 메소드에 포인터를 가지고 있다. 클래스 인스턴스는 각각 메소드에 대한 주소값을 가지고 있으며 VTable에 해당 메소드를 찾아 실행을 한다.

V-Table(Virtual Method Table)

  • 클래스에 메소드를 정의할 때 마다 컴파일러는 해당 타입에 대하여 메소드 주소를 추가합니다.

  • 위에서 설명한 것처럼 컴파일 시점에서 VTable에 해당 클래스에 대한 메소드 주소를 추가한다.

  • 이후 런타임시점에서 해당 인스턴스가 메모리에 할당이 된다.

  • 각각 인스턴스의 메소드가 호출을 한다면 VTable에 저장된 주소 값을 참조하여 해당 코드를 실행한다.
  • 위 사진은 VTable이 각각 나뉘어져있지만 실제로는 각각 상속된 타입이므로 해당 타입을 알아내기 위해서는 모든 테이블을 확인하여 맞는 타입에 대한 메소드를 결정한다.
  • 그렇기 때문에 그만큼의 비용이 증가하여 오버헤드가 발생하게 된다.

정리

  • 컴파일 시점인지, 런타임 시점인지에 따라서 Static Dispatch와 Dynamic Dispatch가 나뉘게 된다.
  • Static Dispatch는 컴파일 시점에서 어떤 메소드를 호출할지 결정되므로 실행속도가 Dynamic Dispatch에 비해 빠르다
  • 다만 객체지향의 특징 중 하나인 다형성으로 인해 비교적 속도가 느린 Dynamic Dispatch를 사용하게 된다.

참고자료

반응형