TIL/Swift 내용 정리
Swift Deep Dive: How to improve Class' performance in Swift?
여의도사노비
2023. 2. 7. 17:47
728x90
- Minimize Overhead
- Keep class structure as simple as possible and limit the number of instance variables to reduce memory overhead.
- Use Value Types
- Use value types (structs and enums) instead of reference types (classes) whenever possible. Value types are faster because they are stored on the stack instead of the heap.
- Minimize Access to the Main Thread
- Minimize time spent on the main thread by performing time-consuming tasks on background threads.
- Avoid Block Captures
- Avoid capturing large blocks when using closures. Capturing a large block can significantly increase the memory overhead of a closure.
- Reduce reference counting
- Reduce the use of string type variables in classes. String is a struct type, but the content of the string is stored on the heap.
- Use Static Dispatch instead of Dynamic Dispatch
- When declaring a class, adding final to a non-inherited class improves performance by preventing further inheritance.
- 오버헤드 최소화
🤖 클래스 구조를 가능한 단순하게 유지하고 인스턴스 변수의 수를 제한하여 메모리 오버헤드를 줄입니다.
🙋🏻♂️ 굉장히 당연한 부분인데... 그렇다고 바로 생각나는 답변은 아닌 것 같다. 쓸데없이 변수(var)를 사용했을 경우 xcode가 알아서 알림을 주듯이... 가능한 단순하게, 가볍게 구조를 유지하는 것은 메모리에 직결하는 문제이다.
- 값 유형 사용
🤖 가능하면 참조 유형(클래스) 대신 값 유형(구조체 및 열거형)을 사용하십시오. 값 유형은 힙 대신 스택에 저장되기 때문에 더 빠릅니다.
🙋🏻♂️ Stack은 LIFO 구조로 메모리의 할당과 해제가 단순하다. 시간복잡도는 O(1)이고 속도가 매우 빠르다. Heap은 동적 할당 방식으로 이루어져있고 할당을 해제하기 위해, 해당 메모리를 재삽입한다. 또한 여러 스레드가 동시에 접근할 수 있기 때문에 동기화에 신경써야한다.
- 메인 스레드에 대한 액세스 최소화
🤖 백그라운드 스레드에서 시간 소모적인 작업을 수행하여 메인 스레드에서 소요되는 시간을 최소화합니다.
🙋🏻♂️ 코드 실행이 오래 걸리는 작업이라면 최대한 백그라운드 스레드에서 실행시켜 줘야 한다. 왜냐하면 메인 스레드에서 실행할 경우 메인 Queue가 연속 Queue라서 작업을 하는 동안 다른 일을 할 수 없어 화면이 멈춰 있기 때문이다(iOS에서 메인 스레드는 UI를 그리는 역할을 한다. 따라서 연속 Queue가 다른 일을 기다리는 동안은 화면이 변하는 동작을 할 수가 없다.). iOS Framework들은 전부 백그라운드에서 실행된다. 필요할 때만 메인스레드에 접근하는 구조(Delegate)이다.
- 블록 캡처 방지
🤖 클로저를 사용할 때 큰 블록을 캡처하지 마십시오. 큰 블록을 캡처하면 클로저의 메모리 오버헤드가 크게 증가할 수 있습니다.
🙋🏻♂️ 클로저는 내부함수와 내부함수에 영향을 미치는 주변 환경을 모두 포함한 객체이다. 클로저는 정의된 주변의 컨텍스트에 있는 상수나 변수들을 캡처할 수 있다. 그런 다음 클로저는 상수와 변수를 정의한 원래 범위가 더 이상 존재하지 않더라도 그 상수와 변수의 값을 참조하고 수정할 수 있다. 즉 클로저는 Reference 타입을 이용하여 값을 캡쳐한다. 이는 곧 캡쳐된 값들이 Heap에 머물게 된다는 것을 의미하고 클로저는 값을 캡쳐할때 기본적으로 강한참조(Strong)를 사용하기 때문에 클로저가 가져온 값을 참조하는 과정에서 강한 순환 문제가 일어난다! 이를 해결하기 위해 weak, unowned 등을 활용하는 방식이 있지만 그래도 놓칠 수 있으니 주의할 것!
- Reference Counting을 줄인다
🤖 클래스에서 스트링 타입의 변수 사용을 줄입니다. String은 Struct 타입이지만 문자열의 콘텐츠를 힙에 저장합니다.
🙋🏻♂️ Struct, Enum 타입, 즉 Value Sematics는 Stack에 보통 Stack에 저장된다. 하지만 이상하게도 String은 Struct 타입임에도 불구하고 Heap에 저장된다. 심지어 Array도 String과 같이 Heap에 저장될 수 있다고 한다. 이는 Copy On Write과 연관이 깊은데 이와 관련된 내용은 레전드 naljin님의 블로그에 정리가 너무 잘되어 있다.
- Dynamic Dispatch 대신 Static Dispatch를 지향한다
🤖 클래스를 선언할 때 상속되지 않는 클래스에 final을 붙이면 더 이상의 상속이 일어나지 않아 성능이 향상됩니다.
🙋🏻♂️ Method Dispatch는 프로그램이 어떤 메소드를 호출할 것인지 결정하여 그 메소드를 호출하는 과정을 뜻한다. 어떤 메소드인지 결정되는 시점에 따라 static과 dynamic으로 나뉜다. Static Dispatch를 지향하는 이유는 Static의 경우 컴파일 시점에 함수를 결정해서 성능상 이점이 있고, Dynamic은 런타임 시점에 함수를 결정해서 성능상 손해가 있다. Static을 명시적으로 사용하기 위해 타입에 static, final을 붙이는 방법이 있다. 그럼에도 불구하고 Dynamic을 써야하는 경우는 언제냐? 그것은 여기서 확인해보자!
https://onelife2live.tistory.com/7