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을 만들지 않아도 되기 때문에)

문제점/고민한점

  1. Class은 상속과 dentity가 중요할때 사용한다고 하는데 identity가 정확히 무엇일까?
  2. Call Stack과 메모리 영역 중 stack 영역은 같은 것인가?

해결방법

  1. 두개의 객체의 프로퍼티 값이 완전히 같더라도 두개의 객체는 다르다. 반면에 값타입 인스턴스는 프로퍼티 값이 같으면 두개의 인스턴스는 같은 것이다. 즉, 타입의 인스턴스가 그 자체로 유일한 것이 identity이다.
  2. 같은 것 같다. stack 영역은 지역변수와 매개변수를 저장한다고 배웠는데, Call Stack도 함수의 지역변수와 매개변수를 Stack frame으로 구분해서 저장하는 곳이기 때문이다.

참고

https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes

https://en.wikipedia.org/wiki/Call_stack