내가 작성하고 있는 코드의 성능을 향상시키려면 항상 다음과 같은 질문을 염두해 두어야 한다.
스택 영역에 값만 저장이 된다.
point1 변수를 point2 변수에 할당시 스택 영역에서 새로운 값이 생성된다.
두 변수가 가지고 있는 값은 다른 값이라서 point2의 프로퍼티를 변경해도 point1에는 아무런 영향을 미치지 않는다.
스택 영역에는 힙 영역을 가리키는 주소값이 저장이 되고,
힙 영역에 실제 값(인스턴스)이 저장이 된다.
구조체와 다르게 클래스는 힙 영역에 두개의 메모리 공간이 더 생기는데, RC값을 저기다 저장한다.(+ 다형성을 위한 type도 저장)
point1 변수를 point2 변수에 할당시 레퍼런스만 전달이 되어서
두 변수 모두 힙 영역에 있는 같은 값을 가리키게 된다.
같은 값을 가리키고 있기 때문에 point2의 프로퍼티를 변경시 point1도 변하게 된다.
→ 의도치 않은 값 변경이 생길 수 있다.
클래스는 구조체보다 비용이 비싼데, 그 이유가 힙 영역을 사용하기 때문이다.
그냥 클래스를 사용하는 것보다 비용이 두배가 들어간다고 한다.
그래서 되도록이면 저 String같은 애들을 구조체로 변경해주면 성능이 올라간다. (String은 내부적으로 힙 영역에 메모리 저장)
static dispatch보다 dynamic dispatch가 비싸다
struct의 경우 static dispatch, class의 경우 dynamic dispatch사용
Drawable 클래스를 상속하는 Point와 Line 클래스를 동시에 담고있는 배열을 만들 수 있다.
배열에는 reference만 담기기 때문에 다 같은 크기라서 괜찮음
컴파일 타임에는 저 draw() 메서드가 Point 것인지 Line 것인지 알지 못하고,,
draw() 메서드를 실행시 virtual table에서 어떤 draw()인지 찾은 후에 전달함
virtual table은 아래 그림처럼 어떤 draw()인지 알고있는 포인터를 가지고 있음
Drawable 프로토콜을 채택하는 Point와 Line 구조체를 동시에 담고있는 배열을 만들 수 있다.
그럼 이때는,, 어떤 struct의 draw가 불려야될지 어떻게 아는걸까??
결론적으로는 Protocol Witness Table에서 찾는데,, 이게 좀 복잡함
생기는 이유는, Drawable 타입을 채택하는 struct들이 다 같은 크기가 아닌데 배열에 저장하려면 같은 크기여야 해서 Existential Container 라는 저장을 위한 레이아웃을 사용하는 것이다.
세가지 부분으로 이루어져있음
여기에는 처음 세개가 value buffer 자리임
그럼 이런 작은 값과 큰 값이 저장되는 방식의 차이를 어케 관리하지?
Value Witness Table로 관리함
이건 각 타입마다 하나씩 가지고 있는데.. (Point와 Line 각각 Value Witness Table 가지고 있음)
Value가 메모리에 할당되고 해제되는 lifetime을 관리하는 역할을 한다고 보면 됨
예로 큰 값의 경우인 Line 타입의 Value Witness Table을 봐보자
Line의 경우 아까 value buffer 설명에서
Existential Container의 value buffer자리에 바로 들어갈 수 없어서
힙에 메모리를 할당하고 힙에 대한 포인터만 Existential Container에 저장했었다.
근데 이 저장하고 메모리에서 해제하기까지 과정이 4단계임
아니 그러면 나머지 하나인 Protocol Witness Table은 언제 사용하는거냐?
그건 바로 copy와 destruct 사이에 draw() 메서드가 실행될 때
Point 타입의 draw() 메서드인지 Line 타입의 draw() 메서드인지 확인하기 위해서 사용하는거다
struct 안에 struct 타입의 프로퍼티를 가진 인스턴스를 복사하는 경우,
저 힙에 있는 메모리가 그대로 복사되어서 메모리를 엄청나게 씀
하 그럼 이걸 또 어케 해결하냐?
저 struct 타입 안의 struct타입 대신 class 타입을 넣는거임
이러케 ㅋ
valueBuffer에 어짜피 reference 들어갈 수 있으니 클래스를 사용하는거지
그럼 복사시 저 힙의 클래스는 복사 안되고 reference만 전달이 되어서 메모리를 아낄 수 있음
근데 또 여기서 문제 ㅎ
클래스 사용시 의도치 않은 값 변경이 일어날 수 있다는 점..
그래서 Copy On Write를 도입하면 된다.
Copy On Write가 무엇이냐면,
클래스를 수정해야 할 때 그 클래스의 RC를 확인해서 만약 RC=1이면 그대로 수정하고,
RC가 1보다 크면 클래스 인스턴스를 새로 생성해서 그걸 수정하는 방식으로 사용하는 것이다.
이렇게 되면 애초에 힙 영역에 Line 인스턴스는 하나만 생성이 되고
Pair 타입 복사시 레퍼런스만 네개되고 다 같은 인스턴스를 참조하고 있어서 훨씬 cheaper 하다 ㅎ
https://developer.apple.com/videos/play/wwdc2016/416/
https://zeddios.tistory.com/597?category=685736