공식문서

Increasing Performance by Reducing Dynamic Dispatch

motosw3600 2021. 12. 9. 15:19

Increasing Performance by Reducing Dynamic Dispatch

다른 많은 언어와 마찬가지로 Swift는 클래스가 수퍼클래스에 선언된 메서드와 속성을 재정의할 수 있도록 합니다. 이는 프로그램이 런타임에 참조되는 메서드 또는 속성을 결정한 다음 간접 호출 또는 간접 액세스를 수행해야 함을 의미합니다. Dynamic Dispatch라고 하는 이 기술은 각 간접 사용에 대해 일정한 양의 런타임 오버헤드를 희생시키면서 언어 표현력을 높입니다. 성능에 민감한 코드에서 이러한 오버헤드는 종종 바람직하지 않습니다.

 

간접호출은 비용이 많이들고 직접 호출보다 퍼포먼스의 속도가 느리다.

아래의 방법을 통해서 dynamic dispatch의 런타임 오버헤드를 줄이자!

1. 더이상 상속이 필요없는 객체에 final을 선언해라

final 키워드는 선언을 재정의할 수 없음을 나타내는 클래스, 메서드 또는 프로퍼티에 설정할 수 있다.

이를 통해 컴파일러는 dynamic dispatch 즉 간접 참조를 안전하게 생략할 수 있다.(속도 up)

아래의 코드에서 point와 velecity는 Stored Property에서 로드를 통해 직접 액세스 되고 updatePoint는 직접 함수 호출을 통해 호출된다. 반면에 update()는 여전히 런타임 시점의 dynamic dispatch를 통해 호출되므로 하위 클래스가 update()를 재정의할 수 있음을 의미한다.

class ParticleModel {
	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
	}

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

 

final을 클래스 자체에 첨부하여 전체 클래스를 final로 선언할 수 있다. 이것은 클래스 자체의 서브클래싱을 금지하여 클래스의 모든 함수와 프로퍼티도 final속성임을 의미한다.

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

 

2. private 키워드를 적용하여 한 파일에서 참조된 선언에 대해 final을 유추해라

private 키워드를 적용하면 접근 범위가 현재 파일에서만 읽도록 제한된다. 이를 통해 컴파일러는 잠재적으로 재정의할 수 있는 모든 선언을 찾을 수 있다. 이러한 재정의 선언이 없으면 컴파일러에서 final키워드를 자동으로 유추해서 메서드 및 프로퍼티 액세스에 대한 간접호출을 생략할 수 있다.

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)
	}
}

현재 파일에 ParticleModel을 재정의하는 클래스가 없다고 가정하면 컴파일러는 private 선언에 대한 dynamic dispatch된 모든 호출을 직접 호출로 바꿀 수 있다.

앞의 예에서와 같이 point와 velocity에 직접 접근하고 updatePoint()를 직접 호출한다.

다시 말하지만, update()가 비공개가 아니기 때문에 간접적으로 update()가 호출된다.

3. 전체 모듈 최적화를 사용하여 내부 선언에서 final을 추론한다

internal 선언(아무 것도 선언되지 않은 경우 기본값)은 선언된 모듈 내에서만 볼 수 있다. Swift는 일반적으로 모듈을 구성하는 파일을 별도로 컴파일하기 때문에 컴파일러는 internal 선언이 다른 파일에서 재정의되는지 여부를 확인할 수 없다.

그러나 전체 모듈 최적화가 활성화된 경우 모든 모듈이 동시에 함께 컴파일된다.

이를 통해 컴파일러는 전체 모듈에 대한 추론을 함께 수행하고 visible한 재정의가 없는 경우 internal 선언에서 final을 추론할 수 있다.

이번에는 ParticleModel에 몇 가지 추가 public 키워드를 추가한다.

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

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

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

var p = ParticleModel()
for i in stride(from: 0.0, through: times, by: 1.0) {
	p.update((i * sin(i), i), newV:i*1000)
}

전체 모듈 최적화로 이 스니펫을 컴파일할 때 컴파일러는 속성 point, velocity 및 method updatePoint()에서 final을 유추할 수 있다. 하지만, update()가 public으로 접근제한이 되어있기 때문에 update()는 final로 유추할 수 없다.

 

※위 글이 쓰였을시 Swift2버전이므로 Swift2에선 public이 open처럼 사용될 때 이므로(open은 Swift3부터 사용) 외부 모듈에서 서브클래싱이 가능하여 final추론 불가

 

원문 : https://developer.apple.com/swift/blog/?id=27

'공식문서' 카테고리의 다른 글

Handling Location Events in the Background  (0) 2022.02.11
Making Changes to Reduce Memory Use  (0) 2022.01.17
Reducing Your App's Memory Use  (0) 2022.01.03
View Programming Guide for iOS  (0) 2021.12.15