Struct vs Class

marisol👩🏻‍💻·2022년 8월 10일
0

Swift 시작할 때부터 계속 들어왔던 struct vs class
면접 단골 질문일것 같아서 이번에 스터디에서 공부했던 내용 정리📝

📌 저장 위치

struct는 값 타입으로, 메모리 구조 중 스택 영역에 저장되고, struct를 새로운 프로프티에 할당할 때마다 값이 복사됩니다.
같은 구조체를 여러 개의 프로퍼티에 할당 한 후 변경하더라도 다른 프로퍼티의 인스턴스에 영향을 주지 않습니다.

class는 참조 타입으로, 메모리 구조 중 스택 영역에 인스턴스의 주소값이, 힙 영역에 인스턴스가 저장됩니다. ARC로 메모리를 관리합니다. class를 새로운 프로퍼티에 할당할 때는 값이 복사되는 것이 아니라 주소값이 복사되기 때문에 클래스의 인스턴스를 여러 프로퍼티에 할당한 뒤 값을 변경하면 모든 프로퍼티의 인스턴스에 영향을 주게 됩니다.

📌 성능

struct의 경우 상속이 불가능하기 때문에 기본적으로 Method Dispatch 타입 중 Static Dispatch를 사용해 컴파일 시점에서 실행되어야할 메서드를 결정하기 때문에 런타임시 오버헤드가 적습니다.
struct가 참조타입을 많이 가지는 경우 RC 오버헤드가 class에 비해 더 많이 날 수도 있지만, Enum을 이용하여 개선 가능하며, 기본적으로는 스택 영역을 사용하기 때문에 성능상 우위를 가질 수 있습니다.

class의 경우 final 키워드를 사용하지 않는 경우 상속이 가능하고, final 키워드를 사용하더라도 컴파일 시점에 내부 프로퍼티나 메서드가 실행되어야 하는지 모르는 경우에는 런타임 시점에 실행할 함수를 결정하는 Dynamic Dispatch를 사용하기 때문에 런타임시 오버헤드가 발생할 수 있습니다.

📌 힙 영역과 스택 영역의 차이

스택은 컴파일 타임에 크기가 결정되고, 끝에서 push, pop을 수행하는 아주 단순한 자료구조입니다.
함수가 호출될 때 지역변수 / 매개변수 등이 스택에 할당되며, 스택 포인터를 감소 시키는 것만으로 메모리를 할당할 수 있습니다. 함수 실행이 끝날 때 스택 포인터를 다시 원래 위치로 증가시켜서 메모리를 해제할 수 있습니다.
스택은 각 스레드별로 할당되어 있어 thread safe를 위한 동기화 작업이 필요 없습니다. 그렇기 때문에 메모리 할당/해제가 매우 빠르게 동작합니다.

힙은 런타임에 크기가 결정되고, 힙에 메모리를 할당할 때에는 인스턴스를 할당할 수 있는 적절한 크기의 블럭을 찾아야 합니다.
이때 여러 스레드가 힙을 공유하고 있기 때문에 thread safe하게 동작하기 위한 동기화 작업이 필요합니다. 이런 이유 때문에 스택에 비해 느리게 동작합니다.

📌 값 타입은 오로지 스택 영역만 사용할까?

값 타입도 힙 영역을 사용하는 경우가 있습니다.
1. 프로토콜을 채택하는 경우, 값 유형이 Existential Container에 저장되고 힙 영역을 사용합니다.
2. 참조 유형을 가지고 있을 때 힙 영역을 사용합니다.
3. 제네릭일 때 힙 영역을 사용합니다.
4. Collection들의 경우, Copy On Write를 하는 경우 수정되기 전까지는 원본의 메모리 주소를 참조하고, 같은 인스턴스를 공유하고 있다가 수정이 일어나는 경우 복사하는 작업을 할 때 힙 영역을 사용합니다.

📌 Method Dispatch?

: 어떤 메서드 구현이 사용되어야 하는지 결정하도록 돕는 매커니즘

  • Static Dispatch: 컴파일러가 어떤 메서드가 실행되어야 하는지 안다는 측면에서 Dynamic Dispatch보다 빠릅니다. 어떤 메서드가 실행되어야 하는지 알기 때문에 컴파일러는 수행할 메서드의 메모리 주소로 바로 점핑할 수 있고, 컴파일러가 inlining과 같은 최적화를 가능하게 해줍니다. (스레드에 있는 스택 태스크(저장, 변경)를 따로 추적하지 않고 메소드의 내부 구현을 바로 가져다 사용할 수 있는 컴파일러 최적화 방법)

  • Dynamic Dispatch: 컴파일 타임이 아닌 런타임에 어떤 메서드가 실행될지 결정하기 때문에 Static Dispatch에 비해 오버헤드가 더 큽니다. 성능이 비싼데도 불구하고 대부분의 OOP 언어들이 Dynamic Dispatch를 지원하는 이유는 다형성을 지원하기 위해서입니다.

📌 struct는 상속 기능을 구현하지 못할까?

Protocol을 사용하면 상속 기능을 구현할 수 있습니다. (이렇게 되면 struct도 Dynamic Dispatch 수행하게 됩니다.)
Swift는 Existential Container라는 저장소를 갖고 있는데, (Value Buffer, VWT, PWT를 가지고 있습니다)
프로토콜을 채택한 타입이 3words를 넘지 않는다면 해당 인스턴스는 value buffer에 저장됩니다.
3words를 넘는다면, 해당 인스턴스는 힙에 저장되고, 그 메모리에 대한 참조를 Exsistential Container에 유지합니다.
Existential Container는 Protocol Witness Table과 Value Witness Table도 갖고 있는데, 해당 테이블의 항목은 실제 타입 구현과 연결되어 있습니다.
이 방식으로 런타임에 Dynamic Dispatch가 가능합니다.
(이 항목의 내용은 WWDC의 Understanding Swift Performance 세션에 자세히 나와있음)

0개의 댓글