Reference Counting
- Heap에 저장된 메모리는 Reference count를 계산하여 0이 되었을 때, 메모리 해제를 안전하게 할 수 있음
- 이 작업은 빈번하게 일어나며, 단순 count를 계산하는 것에서 끝나지 않음
- Reference Count의 증감을 계산할 때 Heap에 다중 스레드 문제가 생길 수 있기 때문에, 단일 스레드만 Count를 계산할 수 있도록 해야함 → Reference Count 계산은 빈번하게 일어나기 때문에 스레드 접근을 제한하는 비용은 유의미함
- → Reference Count를 atomic하게 함으로 안전하게 관리
- nonatomtic이 기본으로 바뀜
atomic
- 다른 프로세스나 쓰레드에 의해 interrupt 되지 않고 수행이 완료될 때까지 무결성을 유지
- 안정성이 올라가지만 처리속도가 낮아질 수 있음
nonatomic
- 멀티쓰레드 환경에서 데이터의 무결성이 보장되지 않아도 될 때 사용
- 안정성이 떨어지지만 처리속도가 높아질 수 있음
값 타입보다 참조 타입을 활용하는 게 더 나은 경우
- Struct(값타입)안에 참조타입이 있는 경우, Struct의 인스턴스가 복사 되어도 결국 같은 주소를 바라보게 되기 때문에 이중으로 Reference Count가 계산되게 됨
- Struct에 참조타입이 있는 경우 Reference의 수에 비례하여 Reference Count 오버헤드가 되는 것
- 둘 이상의 참조타입이 있는 경우 Struct가 아닌 Class를 사용하는 것이 좋다
Method Dispatch
Static Dispatch
- 컴파일 타임에서 구현을 결정
- → RunTime에서 호출 시 이 함수구현부로 바로 점프!
- 컴파일러가 실제로 어떤 구현이 실행될지 가시적으로 보여줌
- 인라인을 통해 코드 최적화를 할 수 있음 → Dynamic Dispatch보다 빠름
- 인라인 : 메서드 호출을 해당 메서드의 본문으로 대체하는 컴파일러 최적화 방법
- 효과가 있다고 판단되는 경우에만 컴파일 시점에 메소드 호출 부분에 메소드 내용을 붙여넣음
- Call stack 오버헤드 줄임
- 짧은 메서드나 루프 안에서 불리는 경우 큰 효과
- 자세한 내용은..https://ios-development.tistory.com/905
- 인라인 : 메서드 호출을 해당 메서드의 본문으로 대체하는 컴파일러 최적화 방법
Dynamic Dispatch
- 컴파일 타임에서 어떤 구현으로 갈 지 직접 결정할 수 없음
- 런타임에서 구현을 찾은 다음 바로 시작
- → Swift에서는 클래스마다 함수 포인터들의 배열인 vTable(Virtual Method Table)이라는 것을 정적 메모리에 저장
- → 하위 클래스가 메서드를 호출할 때, 이 vTable 를 참조하여 실제 호출할 함수를 결정
- 인라인을 통해 코드 최적화 불가능
- Objective-C의 메서드의 경우 동적으로 호출
- → ex) objc_msgSend (anObject, @selector (doMethod:) , aParameter)
- → 강력하고 유연한 특징을 가지고 있지만 성능 저하 요소
- → 특히 Loop 안에서 빈번하게 Method 호출이 일어나는 경우
- 이러한 단점에도 불구하고 Dynamic Dispatch는 polymorphism(다형성)을 가능하게 한다는 장점을 갖음 → 추상화가 가능해짐
→ Drawable을 채택하는 함수들의 포인터를 배열로 저장하여 for구문을 통해 클래스의 draw 메서드를 호출한 상태
→ 컴파일러는 유형을 통해 vTable을 조회
- 클래스는 기본적으로 override가 가능하기 때문에 Dynamic Dispatch으로 메서드를 전달함그래도 결론적으로 Dynamic Dispatch는 성능을 저하시키기 때문에 Static Dispatch로 바꿀 수 있으면 바꿔서 성능 향상 시켜라!
- Dynamic Dispatch는 런타임 오버헤드를 발생시키는 대신 언어 표현력을 높임
Class 성능 향상 시키기
1. Class에 final을 붙이기
// 특정 프로퍼티, 메서드에 final 붙이기
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
}
// final을 붙이지 않았기 때문에 override 가능
func update(newP: (Double, Double), newV: Double) {
updatePoint(newP, newVelocity: newV)
}
}
// class 전체에 final 붙이기
final class ParticleModel {
var point = ( x: 0.0, y: 0.0 )
var velocity = 100.0
// ...
}
2. private 키워드를 적용하여 한 파일에서 참조되는 선언에 final를 유추시키기
// private을 붙이면 사용 범위가 현재 파일로 제한됨
// 이런 경우 컴파일러가 override가능한 모든 선언을 찾을 수 있게 됨
// 서브 클래싱을 하지 않을 것이라고 컴파일러 추론한 경우
// -> 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
}
// private이 없어서 dynamic
func update(newP: (Double, Double), newV: Double) {
updatePoint(newP, newVelocity: newV)
}
}
// 클래스 전체에 붙이는 경우
private class ParticleModel {
var point = ( x: 0.0, y: 0.0 )
var velocity = 100.0
// ...
}
3. 전체 모듈 최적화를 사용하여 내부 선언에 대한 final을 유추
- swift는 기본적으로 컴파일할 때 모듈 내의 파일을 하나씩 컴파일 함
- 전체 모듈 최적화를 활성화시키면 모든 모듈이 동시에 컴파일 됨
- 이때 컴파일러는 전체 모듈에 대해 추론이 가능해져, override가 없는 internal의 선언에 final을 유추 가능해짐
참고 링크
Swift ) (1) Understanding Swift Performance (Swift성능 이해하기)
Understanding Swift Performance - WWDC16 - Videos - Apple Developer
Increasing Performance by Reducing Dynamic Dispatch - Swift Blog
[iOS] nonatomic vs atomic 알아보기
728x90
'학습활동' 카테고리의 다른 글
APFS-iOS File System (0) | 2024.01.08 |
---|---|
KeyChain (0) | 2023.12.11 |
Swift Performance (1) (0) | 2023.12.11 |
URL Loading System (0) | 2023.11.30 |
동시성 프로그래밍 (0) | 2023.11.08 |