Allocation
- Swift는 자동으로 메모리 할당과 해제를 처리합니다. 메모리 할당과 해제는 Stack 또는 Heap에서 처리됩니다.
Stack
- LIFO(Last In First Out)의 단순한 구조로 메모리 할당과 해제가 편리합니다.
- StackPointer를 사용하여 할당, 해제를 처리합니다.
- 단순한 구조를 가진만큼 시간복잡도는 O(1)로 속도가 매우 빠릅니다.
- Stack은 Heap보다 비용이 더 적게 들어가며, 속도가 더 빠른 할당 방법입니다.
Heap
- Stack보다 더 복잡한 구조를 가지고 있습니다.
- Dynamic한 할당 방법을 사용하는데, Heap 영역에서 사용하지 않은 블록을 찾아서 메모리 할당을 처리합니다.
- 할당을 해제하기 위해서는 해당 메모리를 적절한 위치로 다시 삽입해야합니다.
- 여러 Thread가 동시에 Heap에 메모리를 할당할 수 있기 때문에, locking 또는 기타 동기화 메커니즘을 사용하여 무결성을 보호해야합니다.
*무결성 = 데이터의 정확성과 일관성이 보장된 상태(중복이나 누락이 없고, 원인과 결과가 변하지 않는 상태)
Semantics
- Semantics는 메모리 할당 시 Stack, Heap에 어디로 저장할 지 결정되는 기준입니다.
- Semantic은 어떤 타입이나 기호가 내부적으로 어떤 의미인지를 뜻하며, 두 가지 종류로 구분됩니다.
- Value Semantics : Stack에 할당됩니다. (Struct, Enum, Tuple 및 기본타입들이 속합니다.)
- Reference Semantics : Stack에 reference인 주소값을 할당하고, 실질적인 데이터는 Heap에 할당합니다. (대표적으로 Class와 function이 있습니다.)
- 따라서 Class의 성능을 향상시키는 첫 번째 방법은 Heap allocation을 피하는 것입니다.
- Value 타입의 자료형을 사용한다한들, String같은 경우 Heap에 Character타입으로 문자들을 간접으로 저장되기 때문에 String을 사용하게되면 Heap allocation을 사용하게 됩니다.
- 해결 방법으로는 struct를 하나 더 만들어서 key로 사용하는 것입니다.
- 이때 커스텀 객체를 collection에 사용하기 위해 Hashable이라는 프로토콜을 채택해야 합니다.
Reference Counting
- Reference Counting은 참조된 인스턴스의 개수를 세는것입니다.
- String 타입은 Character들을 Heap에 저장하며, UIFont 또한 클래스로 만들어진 객체이므로 reference counting이 발생합니다.
- 즉, 구조체는 기본적으로 레퍼런스를 사용하지 않지만, 구조체 안에서 간접적으로 레퍼런스를 사용하게 되면 reference counting으로 오버헤드를 처리하는 비용이 들게 됩니다.
Overhead
- 어떤 작업을 처리하기 위해 사용되는 간접적인 처리 시간, 메모리 등을 말합니다.
- 구조체의 Reference Counting 오버헤드는 구조체에 있는 레퍼런스 개수에 비례하게 됩니다.
- 구조체에서 하나 이상의 더 많은 레퍼런스를 가지게된다면, Reference Counting 오버헤드가 클래스보다 더 많이 발생하게 됩니다.
- Class의 성능을 향상시키는 두 번째 방법은 Reference Counting Overhead를 최소한으로 줄이는 것입니다.
- 그 방법으로는 UUID를 사용하는 것입니다.
- uuid 프로퍼티는 UUID라는 Struct 타입으로 대체할 수 있습니다.
- uuid의 타입을 UUID로 변경하면, Reference가 줄어들게 됩니다.
- 또한, String 대신 enum을 사용하여 오버헤드를 해결할 수도 있습니다.
Method Dispatch
- Method Dispatch는 프로그램이 어떤 메서드를 호출할 것인지 결정하여 그 메서드를 호출하는 과정을 뜻합니다.
- 어떤 메서드인지 결정하는 시점에 따라, static과 dynamic으로 나뉩니다.
Static Method Dispatch
- 컴파일러 시점에 컴파일러가 메서드의 실제 코드 위치를 파악할 수 있어 런타임에 찾는 과정없이 바로 해당 코드를 실행하는 것을 의미합니다.
- 구현된 코드들이 어디서 실행되는지 알 수 있기 때문에, Method Inlining(메서드 인라이닝)과 같은 코드 최적화를 적극적으로 시행합니다.
*Method Inlining : 메서드를 호출 할 때 해당 메서드로 이동하지 않고 메서드의 결괏값을 바로 반환하여 성능을 향상하는 것.
Dynamic Method Dispatch
- 컴파일 타임에 어떤 메서드를 호출하는지 판단할 수 없어 런타임에 table에 구현을 참조하여 해당 메서드에 대한 정보를 가져와서 코드를 실행시키는 것을 의미합니다.
- dynamic dispatch는 static dispatch보다 많은 비용을 필요로 하지 않고, 레퍼런스 카운팅, 힙 할당과 같은 쓰레드 동기 오버헤드가 없습니다.
- 하지만 컴파일러는 static dispatch에서는 최적화 작업이 가능하지만, dynamic dispatch에서는 컴파일러가 추론할 수 없습니다.
Dynamic Method Dispatch가 필요한 이유?
- dynamic dispatch가 필요한 이유는 다형성 때문입니다.
- 다형성이란, 하나의 객체가 여러 타입을 가질 수 있는것을 의미합니다.
- Class 성능을 향상시키는 세번째 방법은 컴파일러를 통해 클래스에 타입 정보에 대한 포인터를 저장하는 virtual method table을 추가하는 것입니다.
- static memory에 저장하며, 해당 테이블을 통해 메서드를 호출하게 됩니다.
- 추가적으로 SubClass를 만들지 않는다면, final을 클래스 앞에 선언하는 방법도 있습니다, 그럼 컴파일러가 static하게 dispatch 할 수 있으며 SubClass를 만들지 않는다는 의미도 알릴 수 있습니다.
내용 출처
https://corykim0829.github.io/swift/Understanding-Swift-Performance/#
'iOS > iOS' 카테고리의 다른 글
모든 View Controller 객체의 상위 클래스는 무엇이고 그 역할은 무엇인가? (0) | 2022.04.09 |
---|---|
prepareForReuse (0) | 2022.04.08 |
NotificationCenter 동작 방식과 활용 방안 (0) | 2022.04.06 |
OOP, FP (0) | 2022.04.05 |
멀티 스레드(Multi Thread) 구현 시, 고려할 수 있는 방식 (0) | 2022.04.01 |
댓글