iOS. class의 성능을 향상 시킬수 있는 방법들을 나열해보시오.

sanghee·2021년 12월 30일
3

👩‍💻면접 스터디

목록 보기
19/22
post-thumbnail

매주 진행하는 면접스터디에서 아래의 질문들에 대한 정리를 모은 글입니다.
https://github.com/JeaSungLEE/iOSInterviewquestions
[Swift] 스위프트 성능 이해하기 (1) - struct와 class의 성능 차이
Understanding Swift Performance - WWDC16

📌클래스와 구조체의 성능

  1. Allocation: 인스턴스를 생성하면 Stack과 Heap 중 어느 곳에 할당되는지
  2. Reference Counting: 인스턴스를 통해 레퍼런스 카운트가 몇 개 발생하는지
  3. Method Dispatch: 인스턴스에서 메소드를 호출했을 때, 메소드 디스패치가 정적인지 동적인지

📌1. Allocation

Swift는 자동으로 메모리 할당과 해제를 처리한다. 메모리 할당과 해제는 Stack 또는 Heap에서 처리된다.

1. Stack

Stack은 LIFO(Last In First Out)의 단순한 구조로 메모리 할당과 해제가 편리하다. 시간복잡도는 O(1)로 속도가 매우 빠르다. Stack Pointer를 사용하여 할당과 해제를 처리한다.

2. Heap

Heap은 Stack보다 좀 더 복잡하다. 다이나믹한 할당 방법을 사용하는데, Heap 영역에서 사용하지 않은 블록을 찾아 메모리 할당을 처리한다. 할당을 해제하기 위해, 해당 메모리를 적절한 위치로 다시 삽입한다. 여러 스레드가 동시에 Heap에 접근할 수 있기에 Locking 또는 다른 동기화 메커니즘으로 무결성을 보호해야 한다.

무결성

데이터 무결성이란 데이터의 정확성과 일관성이 보장된 상태를 의밓ㄴ다. 정확성이란 중복이나 누락이 없는 상태를 뜻하며, 일관성이란 원인과 결과가 연속적으로 보장돼 변하지 않는 상태를 의미한다.

Semantics

메모리 할당 시 Stack 혹은 Heap에 저장될 때 시멘틱으로 결정된다. 시멘틱이란 어떤 타입, 기호가 내부적으로 어떤 의미인지를 뜻한다. value semantics와 reference semantices로 구별된다.

  1. Value Semantics: Stack에 할당된다. 구조체, 열거형, 튜플 및 기본 타입들이 속한다.
  2. Reference Semantics: 대표적으로 class나 function이 있다.

[String: Any]

딕셔너리에 String 타입의 키를 사용하는 것은 성능에 좋지 않다. String 타입은 값타입이지만, heap에 Character 타입으로 문자들을 간접적으로 저장하기 때문이다. 사용하게 되면 heap allocation이 발생한다.

struct Attributes: Hashable {
    var color: Color
    var orientation: Orientation
    var tail: Tail	
}

let key = Attributes(color: color, orientation: orientation, tail: tail)

해결하는 방법으로는, struct을 만들어서 key로 사용하는 것이다. 이때 Hashable이라는 프로토콜을 채택해야 하는데, 커스텀 객체를 collection에 사용하기 위해 필요하다.

📌2. Reference Counting

Struct에서도 Referenct Counting이?

struct label {
    var text: String
    var font: UIFont
    func draw() {}
}

String 타입은 Character들을 힙에 저장하며, UIFont 또한 클래스로 만들어진 객체이므로 래퍼런스 카운트가 필요하다.

해결

uuid를 String 타입이 아닌 UUID로 변경하여 래퍼런스 줄인다. UUID는 struct 타입이다. String 대신 enum을 사용하여 해결할 수도 있다.

📌3. Method Dispatch

Method Dispatch는 프로그램이 어떤 메소드를 호출할 것인지 결정하여 그 메소드를 호출하는 과정을 뜻한다. 어떤 메소드인지 결정되는 시점에 따라 static과 dynamic으로 나뉜다.

Static Method Dispatch

컴파일 시점에 컴파일러가 메소드의 실제 코드 위치를 파악할 수 있어, 런타임에 찾는 과정 없이 바로 코드를 실행한다. 구현된 코드들이 어디서 실행되는지 알 수 있기에 메소드 인라이닝과 같은 코드 최적화를 시행한다. 메소드 인라이닝(Method Inlining)이란 메소드를 호출할 때 해당 메소드로 이동하지 않고 메소드의 결과값을 바로 반환하여 성능을 향상시킨다.

Dynamic Method Dispatch

컴파일 시점에 어떤 메소드를 호출하는 지 알 수 없어, 런타임에 table을 참조하여 해당 메소드에 대한 정보를 가져와서 코드를 실행한다. Static Method Dispatch보다 많은 비용을 필요로 하지 않고, 래퍼런스 카운팅, 힙 할당과 같은 쓰레드 동기 오버헤드가 없다. 하지만 컴파일러는 Static은 최적화 작업이 가능하지만, Dynamic Dispatch에는 추론할 수 없다.

Dynamic Dispatch가 필요한 이유?

이유는 다형성 때문이다. 다형성(Polymorphism)이란 하나의 객체가 여러 타입을 가질 수 있는 것을 의미한다.

위의 사진을 보면, Point, Line 클래스가 Drawable를 상속하고 있다. 이 상황에서 d.draw의 d가 Point인지, Line인지 알기가 힘들다. 이를 해결하기 위해 컴파일러는 클래스에 타입 정보에 대한 포인터를 저장하는 virtual method table를 추가한다. static memory에 저장하며, 이 테이블을 통해 메소드를 호출한다.

final class

서브 클래스를 만들지 않는다면, final을 클래스 앞에 선언한다. 그러면 컴파일러가 static하게 dispatch할 수 있다. 또한 서브클래스를 만들지 않는다라는 의도도 보여줄 수 있다.

profile
👩‍💻

0개의 댓글