Reducing Dynamic Dispatch

안녕하세요 !! 지난번 포스팅에 이어 Reducing Dynamic Dispatch에 대한 글을 쓰려고 합니다!

지난 포스트에서 소개했던 final 키워드는 Reducing Dynamic Dispatch 중 한가지 방법이라고 말씀드렸었는데요,

이번 포스트에서는 Dynamic Dispatch와 그 외에 다른 방법에 대해 알아보려고 합니다!

더운 여름이지만 힘차게 출발해보죠~!! 💪

Question

Swift와 같은 객체지향 언어에서 대부분 프로퍼티와 메소드를 Overriding하여 사용할 수 있습니다.

그렇다면 프로그램은 Overrding 된 프로퍼티나 함수가 무엇이 사용될 지 어떻게 알게되는 걸까요? 🤔

Dispatch

어떤 프로퍼티나 함수를 호출할지 결정하고 실행하는 과정을 Dispatch 라고 합니다.

그리고 Dispatch는 두가지 방법이 존재합니다.

Dynamic Dispatch

Overriding 되어있는 메소드나 프로퍼티를 Runtime 시점에 어떤 것을 실행할 지 결정하는 것 입니다.

Static Dispatch

Compile 시점에 어떤 메소드를 실행할 지 결정하는 것 입니다.

각각의 예시를 코드를 통해 살펴보겠습니다.

// Static Dispatch
struct RedStruct {
	var color: UIColor = red

	func someMethod() {
		// ..
	}
}

struct는 상속이 되지 않기 때문에 RedStruct의 color 프로퍼티와 someMethod()의 호출은 컴파일 시 명확해 집니다.

따라서 struct에서 정의한 모든 메소드와 프로퍼티는 Static Dispatch가 이루어집니다.

// Dynamic Dispatch
class Color {
	func printColor() {
		// ..
	}
}

class GreenClass: Color {
	override func printColor() {
		// ..
	}

	func someMethod() {
		// ..
	}
}

let greenInstacne: GreenClass = GreenClass()
greenInstance.printColor() // Color.printColor vs GreenClass.printColor()

// let greenInstacne: Color = GreenClass()
// greenInstacne.printColor()

// Static Dispatch
final class BlueClass {
	func someMethod() {
		// ..
	}
}

반면 class는 상속이 가능하기 때문에 문제가 발생합니다.

Color Class를 상속한 GreenClass의 printColor()를 호출할 때에는 Color의 printColor()를 호출해야하는지 GreenClass에 override된 printColor()를 호출해야 하는지 애매해 집니다.

아래의 greenInstacne를 정의하는 부분에서 GreenClass 타입으로 정의하였기 때문에 GreenClass.printColor()가 호출되지만,

그 아래의 주석 처럼 Color 타입으로 정의하게 되면 Color.printColor()가 호출되게 됩니다.

이렇게 RunTime시에 메소드를 결정 할 수 있기 때문에 Dynamic Dispatch가 되는 것 입니다.

만약 Color가 상속이 되지 않는다면 Static Dispatch가 이루어질까요 ??

답은 그렇지 않습니다.

컴파일 시 Color Class를 보았을 때 상속으로 인해 다른 클래스에서 호출 될 가능성이 있다고 판단하여 Dynamic Dispatch로 결정합니다.

그러면 class는 무조건 Dynamic Dispatch라고 생각하실 수 있겠습니다만,

기본적으로 class는 Dynamic Dispatch입니다. 😅

그.리.고 코드의 마지막 부분을 봐주시길 바랍니다 !!

이전에 작성했던 포스팅에서 final은 상속을 막는 키워드라고 하였습니다.

따라서 class가 상속이 되지 않는것을 보장했기 때문에 BlueClass의 모든 프로퍼티와 메소드는 컴파일 시 Static Dispatch로 결정되게 됩니다.

이로써 final을 사용하면 Dynamic Dispatch를 감소시키는 한 가지 방법임을 다시 한번 증명하게 되었습니다. 👏

Why Reducing Dynamic Dispatch

자 이제 Dispatch의 두가지 방법에 대해 알아보았고, 어떤 것인지도 조금 이해가 된 것 같습니다.

그렇다면 Dynamic Dispatch를 왜 줄여야 할까요 ?

이전 포스팅에도 답이 있지만... 조금 더 친절하게 설명 드리겠습니다. 😘

Dynamic Dispatch는 컴파일러의 가시성을 차단하여 최적화를 막고,

vtable을 조회하게 되고, 실행하기에 적합한 메소드를 찾는 비용이 발생하게 됩니다.

다음 사진은 class, final class, struct 각각의 성능을 가시화 한 것이고, Method Dispatch 부분을 봐 주시면 될 것 같습니다.

Method Dispatch 가 Static에서 Performance 측면에서 유리하다고 나와 있습니다.

결론은 Performance를 위해서 Dynamic Dispatch를 줄여야 하는것 입니다. ☺️

Another Way For Reduce Dynamic Dispatch

이제 왜 Dynamic Dispatch를 줄여야 하는지 알아봤으니!!

다른 방법에 무엇이 있는지 살펴볼까요 ?!

Use final when you know that a declaration does not need to be overridden.

포스팅 했었고, 계속 등장 했었던 final 키워드를 사용하는것 입니다!

class ParticleModel {
	// Static Dispatch
	final var point = ( x: 0.0, y: 0.0 )
	final var velocity = 100.0

	final func updatePoint(newPoint: (Double, Double), newVelocity: Double) {
		point = newPoint
		velocity = newVelocity
	}
	// Dynamic Dispatch
	func update(newP: (Double, Double), newV: Double) {
		updatePoint(newP, newVelocity: newV)
	}
}

final을 명시한 프로퍼티와 메소드는 Static Dispatch를 사용하게 되지만,

final을 명시하지 않은 update(newP:newV)메소드의 경우 ParticleModel이 상속 되어 override될 가능성이 있기 때문에 Static이 아닌 Dynamic Dispatch를 사용하게 됩니다.

final class ParticleModel {
	var point = ( x: 0.0, y: 0.0 )
	var velocity = 100.0
	// ...
}

위와 같이 상속이 되지 않는 class에 final을 명시하게 되면, 상속이 되지 않는 class라고 판단하고, Static Dispatch를 사용하게 됩니다.

Infer final on declarations referenced in one file by applying the private keyword.

private, fileprivate 키워드를 적용하는 것은 참조할 수 있는 범위를 현재 파일로 제한하는 의미입니다.

따라서 해당 파일에서 상속이 일어나지 않는다면, 컴파일러는 자동으로 final로 추론하여, Static Dispatch를 사용하게 됩니다.

class ParticleModel {
	private var point = ( x: 0.0, y: 0.0 )
	private var velocity = 100.0

	private func updatePoint(newPoint: (Double, Double), newVelocity: Double) {
		point = newPoint
		velocity = newVelocity
	}

	func update(newP: (Double, Double), newV: Double) {
		updatePoint(newP, newVelocity: newV)
	}
}

위 코드에서도 마찬가지로 private을 명시한 프로퍼티와 메소드는 상속 하여 Override될 수 없기 때문에 Static Dispatch를 사용하게 됩니다.

반면 private이 명시되지 않은 update(newP:newV) 메소드의 경우 Override 될 가능성이 있기 때문에 Dynamic Dispatch를 사용하게 됩니다.

Use Whole Module Optimization to infer final on internal declarations.

마지막으로 WMO(Whole Module Optimization)를 이용하는 방법입니다!

💡WMO란

Swift 컴파일러는 기본적으로 각각의 파일별로 컴파일을 진행하기 때문에 Xcode는 여러가지의 파일을 병렬적으로 컴파일 해 속도가 매우 빠릅니다.

하지만 각각의 파일을 독립적으로 컴파일 함으로써 컴파일러의 최적화를 막게 됩니다.

또한 Swift는 전체 프로그램을 하나의 파일처럼 컴파일 하고, 프로그램을 최적화 할 수 있습니다.

이러한 모드는 swiftc commnad line flag인 whole-module-optimization 에서 활성화 할 수 있습니다.

프로그램 컴파일은 더 오래 걸리지만, 실행은 빠를 수 있습니다.

WMO를 사용하면 전체 파일을 한번에 컴파일 하기 때문에, internal이 override되는것을 확인할 수 있고,

override 되지 않는다면, 자동으로 final을 붙여 dynamic dispatch를 줄일 수 있습니다.

WMO는 Xcode8 부터 release 상태에서만 설정되어있다고 합니다.
Project - Build Settings - Swift Compiler - Compilation Mode에서 확인하실 수 있습니다.

마무리 하며..

첫 포스팅을 작성하게된 계기는 final keyword 때문에 작성하게 되었는데 파고 팔수록 보아야 하는 내용이 많이있네요 !!

"Writing High-Performance Swift Code"를 보면 Swift 코드의 더 좋은 퍼포먼스를 위한 방법이 많이 나와있습니다!!

이번 포스트는 그중에 한가지 방법인 Reducing Dynamic Dispatch를 정리해둔것이지요!

그렇다면.. 당연히 남은 방법들도 정리해보아야겠죠?! 퍼포먼스 좋은 Swift 코드를 위해서~!!

오타나 틀린부분에 대한 지적은 언제나 환영합니다 😆

오늘도 읽어주셔서 감사합니다!! 🙇‍♂️

참조 :

https://developer.apple.com/videos/play/wwdc2016/416/

https://github.com/apple/swift/blob/main/docs/OptimizationTips.rst#dynamic-dispatch

https://onemoonstudio.tistory.com/7

좋은 웹페이지 즐겨찾기