iOS 캠프 특강 [Understanding Swift Performance] 요약
- 2021.03.01 월요일
재르시
- 이재성
- 디자인이 이쁜 앱을 만드는 것을 좋아하고, 게임 좋아하는 iOS 개발자
- 네이버 카페팀 소속
시간복잡도와 공간복잡도 Big-O
시간 복잡도
- 데이터 입력이 충분히 큰 것을 가정하므로 사소한 것은 무시
- 상수항 무시
- 영향력 없는 항 무시 (최고차항만 남김)
공간복잡도
- 공간에 대한 개념
Swift의 시간/공간 복잡도
- Developer Documentation에 Big-O 정보 적혀있음
Array
- reverse(): O(n)
- 현재 배열을 뒤집음
- 느리지만 공간을 덜 차지
- reversed(): O(1)
- 뒤집힌 배열을 반환
- 더 빠르지만 공간을 더 차지
- 배열에서 값이 아닌 참조를 사용하면 NSArray로 사용할수 있어서 컴파일러에서 최적화 불가능
- 값타입(구조체)를 사용하자
- ContiguousArray: C언어 수준의 성능
Dictionary
- 원소에 접근할때 Array보다 빠름
디버깅 용도의 print 함수 처리
디버깅 용도의 print()는 아래처럼 처리해주어 배포버전에서는 빼기 (설정의 Other Swift Flag에서 디버그 플래그 DEBUG로 설정해주기)
func printToConsole(message: string) {
#if DEBUG
print(mesage)
#endif
}
오버플로우 연산자 (&+…)
- 오버플로우 체크하는 것도 퍼포컨스에 영향줌
Stack VS Heap and ARC
Stack
- 지역변수/매개변수 저장
- 함수의 호출과 함께 할당되며, 호출이 완료되면 소멸
Heap
- 동적으로 할당된 메모리는 포인터를 통해 접근
- 변수에 직접 접근하는 것보다 느리다.
- 큰 메모리 풀이므로 큰 배열 클래스를 할당 가능
- 사용자가 직접 관리할 수 있고 해야만 하는 메모리영역
- ARC에서 해줌
- 힙은 할당시에 빈 곳을 찾고 관리하는 복잡한 과정 필요
- 스택은 스택 포인터 변수 값만 바궈주는 정도
ARC
- Swift는 ARC를 사용해서 앱의 메모리 사용량을 추적하고 관리
- Heap 영역을 관리
- thread safe하게 카운트를 Atomic하게 증감해야 한다
Weak VS Strong VS Unowned
weak
- 약한 참조
- 참조하는 인스턴스가 메모리에서 해제되면, ARC는 자동으로 약한 참조를 nil로 설정한다
- 그래서 오버헤드 발생
unowned
- 미소유 참조
- 미소유 참조는 항상 값을 가지고 있는 것으로 간주하기 때문에 ARC는 미소유 참조의 값을 nil로 설정하지 않늗다.
Class VS Struct
Class
- call by reference
- 힙에 저장되어 비교적 느림
Struct
- call by value(할당/전달시 값 복사)
- 스택에 저장되어 비교적 빠름
- class는 주소값만 복사하는 경우 메모리 주소크기 (8바이트)만 복사하면 되지만, struct는 항상 복사하니까 사이즈가 큰 배열을 함수로 값 전달을 해줄때마다 매번 복사가 되면 성능에 문제가 있지 않을까?
- 그래서 COW(Copy On Write) 사용됨
- 평소엔 메모리 주소값만 변경, 내무 값이 변경이 되는 경우에만 복사
- COW를 위해서 class타입을 가지고 있음
- 그래서 copy시 Reference Count 사용
- 값이 변경될 때마다 RC 증가
struct Attachment {
let fileURL: String
let uuid: String
let mimeType: String
}
struct Attachment {
let fileURL: URL
let uuid: UUID
let mimeType: MimeType
}
그럼 언제 Class를 쓸까?
- Value보단 Identity가 중요한 경우
- 상속
Method Dispatch
- 메서드에 대한 실행 코드의 메모리 주소를 찾는 프로세스
- Static/Dynamic 방식
- Dynamic방식에는 Table Dispatch/MessageDispatch 방식이 있음
Static(Direct)
- 가장 빠른 메소드 디스패치 프로세스 중 하나
- 컴파일러가 컴파일중에 호출해야하는 메서드를 알고 처리하는 방법
메소드 인라이닝
- 항상 단일 작업 전용으로 작은 함수를 만드는 것이 좋다
- 그러나 코드를 작은 함수로 분리하면 성능 비용이 발생
Dynamic
- 클래스의 모든 메서드 주소를 저장하기 위해 포인터 배열이 생성됨
- 이 포인터 배열은 테이블로 호출되며 런타임시 테이블 조회에 의해 적절한 동적 메서드 호출이 발생
Static VS Dynamic
- class에 final 키워드를 사용하면 Static 사용
Leave a comment