TIL(Today I Learned)
3월 1일(월)
학습내용
- Kahoot
- iOS 환경에서 특정 이벤트에 응답하는 주체는 Responder이다.
- First Responder를 결정지을 이벤트는 Touch, Press, Shake motion 등이 있다.
- 응답체인의 마지막 응답후보는 Application Delegate이다.
- UIEvent.EventType에는 motion(shake), remoteControl, presses 등이 있다.
- iOS 파일 시스템에서 백업되는 영역은 Document, Application Support 등이 있다. (tmp, cache는 백업되지 않는다.)
- iOS 파일 시스템에서 사용자에게 노출되는 영역은 Document이다.
- Understanding Swift Performance with 재르시
- 시간복잡도, 공간복잡도
- 시간 복잡도는 시간에 대한 개념이며 Big O 표기법을 사용해서 나타낸다. Big O는 증가하는 비율을 나타내는 개념이며, 데이터의 입력이 충분히 큰것으로 가정한다. 그래서 상수항이나 영향력이 없는 항은 생략한다.
- 공간 복잡도는 공간에 대한 개념이며 마찬가지로 Big O 표기법을 사용한다.
- O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) … 순이며, 오른쪽으로 갈수록 복잡도가 큰 것이다.
- Array
- Array의 reverse() 시간 복잡도는 O(n)이고, reversed()는 O(1)이다. 같은 기능이라도 어떤 메서드를 사용하냐에 따라 성능차이가 있을 수 있다(완전히 같지는 않지만…). 반면에 sort()와 sorted()는 둘 다 복잡도는 O(nlogn)이다.
- 배열의 element 타입을 참조 타입으로 설정하면 NSArray가 사용될 수 있기 때문에 컴파일러에서 최적화가 불가능하다고 한다. NSArray는 연속적으로 데이터를 저장하지 않는 경우가 있어서 다음 데이터를 찾아야하는 오버헤드가 발생할 수 있다고 한다.
- ContigousArray를 사용하면 항상 메모리에 연속적으로 저장된다고 한다. (참조타입이여도)
- Array는 element가 클래스(참조타입)일 경우, 실제론 NSArray로 저장 될 수 있다고 한다. NSArray는 potentially non-contiguous라고 한다.
- 기타 성능 향상 방법
- print문도 간단해 보이지만 꽤 복잡한 로직으로 작동되므로 배포하는 앱에서는 print가 되지 않도록 한다. (전처리기를 잘 이용해서 디버그할때만 print가 작동하도록 하자. 성능이 1600배까지 차이가 날 수 있다고 한다.)
- Unchecked Operations은 오버플로우가 발생할지를 체크하지 않는 연산자를 의미하며, ‘&+’, ‘&-‘, ‘&*’ 이 있다. 이 연산자는 오버플로우를 발생하면 뒷값을 자르고 연산을 수행한다. 오버플로우를 체크하는 것도 오버헤드가 될 수 있으므로 Unchecked Operations를 사용하여 오버헤드를 줄일 수 있다.
- Dimensons of Performance
- Stack VS Heap
- Reference Counting이 실행되는 정도
- Method Dispatch: Static VS Dynamic
- Heap 할당은 빈 메모리를 찾고 관리하는 복잡한 과정이 필요하다. 그리고 Thread Safe 해야하므로 lock 등의 동기화 동작이 필요하므로 성능 저하가 발생한다. 반면에 Stack은 스텍 포인터 변수 값만 바꿔주면 되므로 오버헤드가 적다.
- ARC는 힙 메모리를 관리하기 위한 방법이다. Atomic하게 Reference Count를 증감해야 한다. Reference Count는 오버헤드가 될 수 있다.
- weak 변수는 참조하는 인스턴스가 메모리에서 해제되면 ARC에 의해 자동으로 nil로 설정된다. 반면에 unowned는 미소유 참조의 값을 nil로 설정하지 않는다. 메모리에서 해제되고 nil로 설정하는 것 자체가 오버헤드가 될 수도 있다.
- Class vs Struct
- Class는 Call by Renference이며, Heap 메모리에 할당된다. Reference Counting을 사용한다.
- Struct는 Call by Value이며, Stack에 할당된다.
- Class는 value 보다 Identity가 중요한 경우, 상속이 중요할 경우 사용한다. 이외의 경우는 성능상 Struct를 사용하는 것이 좋다.
- COW(Copy-On-Write)
- 메모리의 주소 값만 복사했다가, write가 필요하면 값을 복사하는 방법이다.
- String, Array, Dictionary는 값타입이지만, COW을 위해서 class 타입을 가지고 있다. Copy 시(레퍼런스 변수 값을 다른 변수로 대입 시) Reference Counting이 동작한다. (String의 사용은 Reference Counting을 증가시키므로 오버헤드가 발생한다.)
- Method Dispatch
- 메서드에 대한 실행 코드의 메모리 주소를 찾는 프로세스이다.
- Static(Direct Dispatch)/Dynamic Dispatch방식이 있으며, Dynamic Dispatch은 Table/Message Dispatch방식이 있다.
- Swift는 Direct Dispatch, Table Dispatch, Message Dispatch를 지원한다.
- Static은 가장 빠른데, 컴파일타임에 어떤 메서드를 호출해야할지 결정되기 때문에 런타임에 실행할 메소드를 찾는 시간이 없어서 빠르다.
- 메소드 인라이닝은 컴파일 시점에 메소드 호출 부분을 메소드 내용으로 붙여넣는 방법이다. 이를 사용하여 call stack 오버헤드를 줄일 수 있다.
- class에 final, private 키워드를 사용하면 Dynamic Dispatch가 아닌 Static이 된다. (상속을 막으므로 다형성이 불가능하여 V-Table을 만들지 않아도 되기 때문에)
- 시간복잡도, 공간복잡도
문제점/고민한점
- Class은 상속과 dentity가 중요할때 사용한다고 하는데 identity가 정확히 무엇일까?
- Call Stack과 메모리 영역 중 stack 영역은 같은 것인가?
해결방법
- 두개의 객체의 프로퍼티 값이 완전히 같더라도 두개의 객체는 다르다. 반면에 값타입 인스턴스는 프로퍼티 값이 같으면 두개의 인스턴스는 같은 것이다. 즉, 타입의 인스턴스가 그 자체로 유일한 것이 identity이다.
- 같은 것 같다. stack 영역은 지역변수와 매개변수를 저장한다고 배웠는데, Call Stack도 함수의 지역변수와 매개변수를 Stack frame으로 구분해서 저장하는 곳이기 때문이다.
참고
https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes