Swift Performance (Allocation)
퍼!!!!!포먼스
평소에도 struct와 class.. 차이를 뭐 대충 알고있다고 생각했는데..
이번엔 성능 차이에 대해 좀 자세히 알아보려 한다.
관련해서 좋은 글들이 많으니.. 내 글은 딱히.. 퀄리티가 좋다는 생각이 안들어서 다 읽으라곤 못하겠다
여튼.. 이번엔 Allcation, Reference Counting, Method Dispatch 정도까지 알아보려한다. 프로토콜 제네릭 디스패치쪽은 다음에..
https://developer.apple.com/videos/play/wwdc2016/416/ 해당 wwdc를 참고하였다.
키노트있어서 그림 안그려도 되겠다^^ 개꿀
일단.. 위에서 말한 순서대로 가보자
아 그 전에 성능표를 보자
왼쪽으로 갈수록 성능이 좋은 것이고, 오른쪽으로 갈수록 성능이 좋지 않은 것이다.
우리가 알아볼 것은 세가지이다.
위에서 부터 하나씩 보면..
- 내가 만든 타입의 인스턴스가 어느 곳(Stack, Heap)에 할당되는지
- 특정 인스턴스를 얼마나(Count) 참조(Reference)하는지
- 인스턴스 메소드를 호출할 때 Dynamic Dispatch, Static Dispatch 둘 중 어느 방식을 통해 호출되는지
고고
Allocation
Swift에서의 메모리 할당, 해제는 Stack, Heap에서 처리된다. 것도 자동으로
이는 우리가 어떤 식으로 일반화, 추상화를 하냐에 따라 달라지고, 그에 따른 성능도 확연히 차이난다.
먼저 Stack과 Heap 메모리에 대해서 좀 알고 가야겠다.
Stack은 Last In First Out 구조로 메모리 할당과 해제가 단순하다. 굳이 검색할 필요가 없이 마지막으로 들어간 것이 먼저 나오는 구조이므로 처리 속도가 매우 빠르다.
위 그림처럼 마지막에 들어온 것이 첫번째로 나가기 때문에 스택의 TOP 부분에 포인터를 위치시켜 Push, Pop을 구현할 수 있다.
스택 포인터를 단순히 줄임으로써 메모리를 할당할 수 있고, 스택 포인터를 다시 올림으로써 메모리 할당을 해제시킬 수 있다.
스택은 메모리의 높은 주소에서 낮은 주소의 방향으로 메모리가 할당된다.
할당된 Y = 1이 TOP 일 것이고 기존 TOP의 주소값이 낮아졌을 것이다.
그래서 항상 TOP에 위치하는 스택 포인터의 주소값을 낮추어 메모리를 할당하고, 해제할 땐 스택의 포인터값을 할당 되기전에 주소값으로 다시 올리면 해제가 되는 구조인 것이다.
Heap 의 경우 메모리를 할당하려면, Heap의 구조를 탐색하여 사용되지 않은 적절한 크기의 메모리를 찾아 할당한다. 이 과정에서 "탐색"의 비용이 들어간다.
그리고 작업이 끝나고 메모리에서 해제시킬 때 사용한 메모리를 적절한 위치로 되돌려 놓아야한다. 이 과정에서 "삽입"의 비용이 또 들어간다.
하지만 힙의 가장 큰 비용은 여러 쓰레드가 힙의 메모리를 동시에 사용할 때 무결성을 보호하기 위해 locking 등의 동기화 메커니즘을 사용해야한다는 것이다.
여기서 무결성이란 여러 쓰레드가 동시에 힙을 사용할 때 해당 힙에 할당된 데이터가 정확하지 않게되는 문제가 발생할 수 있는데, 이를 방지한 것, 즉 정확한 데이터를 유지하는 것을 무결성이라고 한다.
그럼 위에서 Stack과 Heap을 대충만 비교해봐도 stack의 성능이 압도적임을 알 수 있지요?
이로 인해 우리가 메모리를 어느 곳에 할당하냐가 성능적으로 중요한 차이임을 알 수 있다!!!
아래 코드를 보자
Call by Value 타입의 인스턴스는 Stack에 할당이 된다.
Call by Value를 따르는 타입으로 struct, enum, tuple 등의 기본 타입들이 있다.
struct 인스턴스를 생성하여 특정 변수(let point1)에 할당하면, 인스턴스가 가졌던 값들이 전부 복사가 되어 할당된다.
그리고 위 코드에선 복사된 인스턴스가 할당된 변수를 또 다른 변수에 할당시켜주는데 이 과정에서도 복사가 이루어진다.
복사된 인스턴스의 프로퍼티를 변경한다고 원본 인스턴스에 영향을 주지 않는다!!!
위 코드에서도 point2.x = 5 하니 해당 인스턴스의 값만 변경이 되지 원본값엔 영향을 미치지 않는 것을 볼 수 있다.
현실서 예를 들면.. 사본 찢어버렸다고 원본도 찢어지진 않는 것처럼 이와 동일히 동작한다고 생각하면 편할 것이다.
Reference Counting은 Heap과 관련이 있는데, Heap을 사용하지 않기때문에 Reference Counting과도 관련이 없다.
위 코드의 경우 함수 실행이 완료되면 스택에서 알아서 해제된다.
따로 뭐 해주는 것도 없고 속도도 빠르니 당연히 성능은?!? 좋겠지
이제 저 코드를 클래스로 만들어보자
Call by Referece의 경우 Stack, Heap 두 곳의 메모리를 차지하는데, 스택엔 Heap을 가리키는 주소값이 할당되고, Heap에는 실제 데이터가 할당되어 있다.
클래스의 경우 Call by Value 타입들과 달리 Class 인스턴스를 생성하여 특정 변수에 할당하면 특정 변수는 해당 클래스 인스턴스가 위치한 힙의 주소를 가진다.
특정 변수를 다른 변수에 복사한다면, 특정 변수가 갖고 있던 힙의 주소를 다른 변수도 갖게 되고, 결국 같은 주소를 참조하게 되어, 복사된 인스턴스를 수정하면 원본 인스턴스도 수정되게 된다.
데이터!!!!를 복사하는 것이 아니라 주소를 복사하는 것이다.
클래스 인스턴스를 만들면 Swift는 무결성을 위해 Heap을 Lock하고 적절한 크기의 메모리 블록을 찾아 할당하고, 해제할 때도 Heap을 Lock하고 사용하지 않은 메모리 블록을 적절한 위치로 재위치 시킨다.
이후 힙 주소를 가진 스택 메모리를 Pop할 수 있다.
이렇게.. 클래스는 힙에 할당을 해야하고, 힙에 할당함으로써 따르는 비용이 구조체보다 훨씬 많이 드는 것을 알 수 있다.
재밌는데 어려운..
'iOS > Swift' 카테고리의 다른 글
Swift) CoreData (0) | 2022.02.20 |
---|---|
Swift) UIAlertController ActionSheet Programmatically (0) | 2022.02.20 |
Swift) final (0) | 2022.02.20 |
Swift) Frame vs Bounds (0) | 2022.02.20 |
Swift) non-Escaping Closure vs Escaping Closure (0) | 2022.02.20 |
댓글