티스토리 뷰

반응형

안녕하세요 :) Zedd입니다.

저번글이 OptimizationTips였는데요, 이걸 제가 완벽히 이해하지 못한 부분이 딱 하나 있는데, 바로 


"Swift에서 타입은 값타입(구조체, 열거형, 튜플..) 및 참조타입(클래스)의 두가지 카테고리로 나눌 수 있습니다. 주요 차이점은 값타입은 NSArray내부에 포함될 수 없다는 것입니다."


음, 보통 막 Swift에서 클래스와 구조체의 차이!!라고 하면, Swift ) Swift 기초문법1 ( CLASS / STRUCT / ENUM )서 나와있듯이, 뭐 상속을 할수있냐 없냐 뭐 값타입이냐 참조타입이냐 보통 저는 그렇게 알고있었거든요!!!!!!!!!!!!!!!!!!!!!!!!!!!! 

근데 갑자기 문서에서 "주요 차이점은 값타입은 NSArray내부에 포함될 수 없다는 것입니다."(A key distinction is that value types cannot be included inside an NSArray)라고 하니까 음? 싶었어요.


그리고 애초에 Swift에서 NSArray를 쓸 일이 있나?

원문을 보면 

"In Swift, types can be divided into two different categories: value types (structs, enums, tuples) and reference types (classes). A key distinction is that value types cannot be included inside an NSArray."


그리고 직접 해보면 NSArray에 값 타입도 잘 담겨서 (Swfit에서) 도대체 제가 뭘 놓치고 있는건지, 뭐가 맞는건지........

그러다 WWDC 2016 Understanding Swift Performance까지 오게되었습니다....ㅎ

혹시 문서에서 "In Swift, types can be divided into two different categories: value types (structs, enums, tuples) and reference types (classes). A key distinction is that value types cannot be included inside an NSArray." < 이렇게 말한 이유가 뭔지 아신다면 댓글로 남겨주시면 정말로 진짜...감사드리겠습니다...


OptimizationTips을 공부한 다음이기도 하고, 지금 공부하기 딱 좋을 것 같아서 해보려고 합니다. :)



Understanding Swift Performance



제목이 Swift "성능" 이해하기잖아요?

Swift의 추상화 메커니즘이 성능에 미치는 영향을 이해하는 가장 좋은 방법은 기본 구현을 이해하는 거에요.

우리가 추상화를 만들고, 추상화 메커니즘을 선택 할 때, 3가지를 생각해야하는데요.



1. 내 인스턴스가 Stack에 할당되는지, Heap에 할당되는지?

2.. 내가 인스턴스를 전달 할 때, 얼마나 많은 레퍼런스 카운팅 오버헤드가 일어나는지?

3. 내가 내 인스턴스의 메소드를 호출 할때, 그게 static dispatch를 통해 일어나는지, dynamic dispatch를 통해 일어나는지?


오른쪽으로 갈 수록 성능은 저하되고, 왼쪽으로 갈 수록 성능은 좋아집니다. 



그럼 할당(allocation)부터 시작합시다.



allocation



Swift는 자동으로 메모리를 할당하고, 해제하는데요. 메모리 중 일부는 Stack에 할당됩니다.

다들 아시다시피, Stack은 LIFO구조로 매우 단순한 데이터 구조입니다.

Stack의 마지막, 즉 Top으로만 Push가 가능하며, 역시 Top에서만 Pop이 가능합니다.

그러니, Stack끝에 포인터만 유지하면, Push및 Pop을 구현 할 수 있게 됩니다. 이 포인터를 Stack 포인터라고 불러요.


그래서, 우리가 함수를 호출 할 때, 우리는 메모리를 일단 먼저 할당해야겠죠? 그 메모리를 단순히 Stack 포인터가 가리키고 있는 곳을 단순히 줄임(decrementing)으로써 필요한 메모리를 할당 할 수 있어요. 그리고 함수가 끝나면, Stack 포인터를 줄이기 전에 있던 곳으로 증가(incrementing)시킴으로써 그 메모리 할당을 해제할 수 있죠.



구구절절 말했지만, 모두 O(1)시간으로 굉장히 빠릅니다.


하지만 이 Stack과는 대조적으로 Heap이라는 데이터 구조가 있는데요.

Heap은 Stack보다는 더 동적(dynamic)이지만, 효율은 Stack보다 덜하죠.


Heap을 사용하면 Stack이 할 수 없는 일을 할 수 있죠.

바로 dynamic lifetime을 가진 메모리를 할당 할 수 있답니다.



Heap에 메모리를 할당하려면, 실제로 Heap 데이터 구조를 검색(search)하여 사용되지 않는 적절한 크기의 블록을 찾아야해요.

그리고 모든 작업이 끝나고, 할당을 해제하려면 해당 메모리를 또 적절한 위치로 다시 삽입(reinsert)해야해요.

검색하고..재삽입하고..딱봐도 Stack보다 복잡한 것을 볼 수 있습니다. 하지만 지금말한 것들은 Heap 할당과 관련된 주요 비용들은 아니에요;;;;

가장 큰 복잡함은, 여러 쓰레드가 동시에 Heap에 메모리를 할당 할 수 있기때문에, Heap은 locking 또는 기타 동기화 메커니즘을 사용해서 무결성(integrity)을 보호해야합니다;; 이게 Heap 할당에서 가장 큰 비용이라고 할 수 있어요.


자, 우리의 프로그램에서 메모리를 할당하는 장소(Stack or Heap)을 조금 더 신중히 생각하면, 성능이 크게 향상될 수 있어요!

코드 하나를 살펴봅시다.



일단, Point라는 "구조체"를 만들었다는 점을 주목해주세요.

Point구조체에는 x, y프로퍼티와 draw()라는 메소드가 있네요.

point1에 Point인스턴스를 만들어서 넣고, point2에 point1를 복사하고, point2의 x프로퍼티에 5를 넣네요.

(이 익숙한 시나리오...)



우리가 코드를 실행하기 전에!!! point1인스턴스와 point2인스턴스에 대해, Stack에 공간을 할당했어요.

Point는 Struct이므로, Stack의 line에 저장됩니다.( x and y properties are stored in line on the stack)



따라서, 우리가 Point(x: 0, y: 0)인 인스턴스를 만들 때, Stack에 이미 할당한 메모리를 초기화 하는 것 뿐이에요.

point2에 point1을 할당할 때, 우리는 단지 해당 point에 대한 복사본을 만들고 Stack상에 이미 할당한 메모리를 다시 초기화합니다. 

즉, point1과 point2는 독립적인(independent) 인스턴스입니다.



자, 그리고 point2의 x프로퍼티에 5를 넣었네요. 



하지만, point1과 point2는 독립적인 인스턴스이기 때문에, point2의 x프로퍼티가 5가 되었다고, point1의 x프로퍼티가 5가 되진 않습니다.

정말 많이 본 시나리오죠? call by value설명 할 때 많이 나오는..그런 코드입니다.



우리가 point1을 사용하고, point2를 사용하고 이제 다 끝나면, Stack 포인터를 점차적으로 증가시키면 point1과 point2에 대한 메모리 할당을 쉽게 해제 할 수 있게 되죠.

정말 간단하죠?


그럼 똑같은 코드를 class로 만들어보도록 하죠.



자, Point라는 클래스를 선언했고, 프로퍼티와 메소드는 아까와 같네요. 

코드를 실행하기 전에, 이미 Stack에 point1과 point2에 대한 메모리 할당이 됩니다.

하지만, 실제로 아까처럼 Stack의 line에 프로퍼티를 저장하는것이 아닌, point1과 point2에 대한 레퍼런스를 위해 메모리를 할당한거에요.

바로 우리가 Heap에 할당할 메모리에 대한 참조인 것입니다!!


따라서 우리가 Point(x: 0, y: 0)이라는 인스턴스를 만들면, Swift는 Heap을 lock하고(무결성을 위해) 해당 크기의 메모리 블록을 검색합니다.



자, 공간 찾았어!!!!!



어 근데 이상한점이 하나 있네요. Stack에서는 x, y 딱 두개만 할당을 받았(?)는데, Heap에서는 4개를 할당받네요.

이 이유는 Point가 클래스이기때문에, x와 y를 저장할 공간 이외에도, Swift가 우리를 대신하여 관리 하기 위한 공간을 더 할당합니다.

point2에 point1을 넣을 때, struct때 처럼, point의 내용을 복사하지 않아요. class에서는 대신 "레퍼런스"를 복사합니다. 



그래서 point2의 x프로퍼티에 5를 넣어도, point1과 point2가 똑같은 곳을 참조하고 있기 때문에, point1의 x값도 5가 나오는 것이죠.

이 시나리오도..많이 봤죠. call by reference할 때 많이 나오죠.


자 이제 point1도 다쓰고 point2도 다썼어!!!



그러면 Swift는 또 Heap을 잠그고(lock) 사용하지않은 블록들을 적절한 위치로 재삽입합니다.



그리고나서야 Stack을 Pop할 수 있습니다.


자..우리가 지금 간단하게 struct, class로 메모리 할당을 봤는데요.

딱 봐도 class는 Heap할당을 필요로 하기 때문에, class가 struct보다 생성하는데 비용이 더 많이듭니다.

class는 Heap에 할당되고, 레퍼런스 체계를 가지므로, class에는 ID및 간접 저장소(indirect storage)와 같은 특성이 있어요.


근데 우리가 추상화 할 때 이런 특성이 필요없다!!!!!!!!!!하면 우리는 struct를 사용하는 것이 좋죠.

Swift ) Swift 기초문법2 ( CLASS / STRUCT / ENUM )에 언제 struct를 쓰면 좋은지 적어놨으니 참고하세요 :)


자, WWDC에서 한가지 더 예를 들어줬는데요,





enum의 case들을 이용해서 여러가지 모양의 말풍선을 만드는 코드에요.



이 makeBalloon코드는 사용자가 스크롤중에 빈번히 호출한다고 가정할게요. 그래서 빠를 필요가 있고 cache를 dictionary로 만들었어요. 즉, 같은 말풍선을 한번 더 만들 필요가 없습니다. (dictionary는 중복이 없죠?) 한번 만들었으면 dictionary에 있을테니, 꺼내 쓰면됩니다.


Key는 String이고, Value는 UIImage네요.

Key를 유심히 보겠습니다. 

"String"이란 타입은 여기서 강력한 타입이 아닙니다. 내가 그냥 이 cache라는 dictionary에 key로 "zedd"를 넣을수도 있죠. 안전하지 않죠!!!

또한 String은 실제로 Heap에 간접적으로 캐릭터들의 contents를 저장하기 때문에 많은 것을 나타낼 수 있어요. 이 말은 즉슨, 우리가 cache라는 dictionary를 야심차게 만들었지만, cache가 hit되어도 Heap할당이 발생한다는 것을 의미합니다.ㅠ


이걸 더 개선해볼까요?


자, Attributes라는 struct!!!!를 만들고 dictionary의 key가 될 수 있도록 Hashable을 준수시켜줬어요.

그리고 이 Attributes를 우리 cache dictionary의 key타입으로 주었습니다.


자, 이것은 뭘 의미하나요??!

우리가 아까 배운대로, Attributes는 struct이기 때문에, Stack의 line안에 프로퍼티들을 할당할거에요. 즉!!!!!!!!!!!!!

Heap할당이 필요없게 됩니다. 즉, 메모리 할당에 대한 오버헤드가 없습니다.

그리고 String때와 달리, 제가 실수로 "zedd"를 key로 넣을 위험도 사라졌네요 :)

이렇게 안전하고 Heap할당이 없으니 훨씬 더 빨라질거에요.


실제로 어떻게 코드를 개선시켜야 할 지 감이 오시나요?


두번째로 레퍼런스 카운팅으로 넘어가도록 합시다.


Reference Counting



방금 Heap을 이야기 할 때, 레퍼런스 카운팅 이야기도 아주 잠깐 나왔었어요.


Swift는 Heap에 할당된 메모리를 할당 해제 하는 것이 "안전"하다는 것을 어떻게 알까요? 

정답은...iOS개발자라면 많이 들어봤을...ㅎ

Swift가 Heap에 있는 인스턴스에 대한 총 레퍼런스 카운트를 유지합니다.


이 레퍼런스 카운트가 어디있냐? 그 인스턴스 "안에" 있습니다.

인스턴스 자체에 레퍼런스 카운트를 유지합니다.


레퍼런스를 추가하거나 제거하면, 해당 레퍼런스 카운트가 증가 / 감소합니다.

이 카운트가 0이 되면 Swift는 아무도 Heap에서 이 인스턴스를 가리키고 있지않으며, 메모리를 할당해제 하는것이 안전하다는 것을 알게 되죠.


너무 진부한 이야기인가요..?;;;



우리가 레퍼런스 카운팅에서 염두에 두어야 할 중요한 것은, 실제로 단순히 +1, -1하는 것 이상으로 이 카운팅 작업은 실제로 빈번히 수행됩니다.

먼저, 단순히 증가 / 감소를 위해 몇가지 간접적인 단계가 있습니다. 그러나 더 중요한 것은, Heap할당과 마찬가지로, 레퍼런스가 여러 쓰레드에 의해 동시에 추가 / 제거 될 수 있기때문에 Thread safety를 고려해야합니다. 실제로 atomically하게 (이걸 번역하자니 좀..) 레퍼런스 카운트가 증가 / 감소되어야 해요. 그리고 참조 카운팅의 빈도로 인해 이 비용이 증가할 수 있어요.


자, 이제 우리의 Point class로 가봅시다.



자, class이니 Heap할당이 일어날 것이고, 레퍼런스 카운트가 있게 될 것입니다.

이걸 수행하면 실제로 Swift컴파일러는 



이런 코드들을 삽입하죠.

아까 인스턴스 자체에 레퍼런스 카운트를 유지한다고 그랬죠?

그래서 Point class안에 refCount라는 프로퍼티가 생겼고, point1을 point2에 넣을 때, 레퍼런스 카운트가 하나 증가한 것이니 retain을 시켜주고, point1, point2를 각각 다 사용하면 release를 시켜주는 것을 볼 수 있습니다.



하나씩 볼까요?



자, 아까처럼 또 코드가 실행되기도 전에 point1과 point2에 대한 메모리 공간을 Stack에 할당하고,



여기서 Heap할당이 시작됩니다. 검색하고..뭐 그러겠죠?



자, 그럼 레퍼런스 카운트가 1이 됩니다.



여기서 point2에 point1을 넣지만, 아직 refCount는 1이죠.

아직 retain을 안해서!!!

저는 여기서 바로 2가 되는줄 ㅎ



자 point2에 대해 retain을 하면, 그제서야 refCount가 2가 됩니다.


point2의 x프로퍼티를 5로 만들면 Heap의 x부분이 5가 될거고...



point1을 다 쓰면, point을 release합니다. 그러면 refCount가 1이되고,



역시나 point2도 release를 하면 refCount가 하나 더 줄어 0이 됩니다.

이때 Swift는 알죠. 

메모리 해제를 해도 ㄱㅊㄱㅊ하다는 것을..



왜냐? 이 point인스턴스를 사용하는 참조가 더이상 없으니까요.

Swift는 Heap을 lock하고, 해당 메모리 블록을 반환하게 됩니다. 



이 레퍼런스 카운팅에 대해서 Point struct로 이야기 해볼까요?

헙..근데 struct에 Heap할당이 있었나요,.,,,,???

없었죠!!!!!!!!!!!!!!!!!!!!!! 즉 struct는 레퍼런스 카운팅에 대한 오버헤드가 없습니다.


근데 Point struct 넘나 간단해씀ㅋ;;;;;

우리가 실제로 쓰는건 더 복잡한건뎁...;;;



흔한 애플이 복잡(complicated)하다고 내놓은 struct...


아무튼 계속 봅시다. 

Label이라는 struct를 만들고 그 안에 String타입, UIFont타입. 

참고로 UIFont는 class입니다.

그리고 앞에서 이야기 했다 시피, String은 contents를 Heap에 저장합니다. 

(여담이지만, Swift의 기본타입은 대부분 struct입니다. Int나 Bool이나 뭐 그런것들이요. String도 마찬가지입니다. 그래서 저는 위에서 말풍선 예제가 나왔을 때 뭐가 잘못된건지 몰랐어요. String은 기본타입이니 처음부터 Hashable이고 struct잖아요? 근데 대안책이라고 나온것도 struct에 Hashable을 준수시켜준건데..뭐가 다르다는 거지?..라고 생각했는데, String이 contents를 Heap에 저장한다고 하면 모든게 다 이해가 가네요 ㅎㅎ)


자 다시!!!!! 

String은 contents를 Heap에 저장하는데 ==  레퍼런스 카운트를 계산해야함 ㅎ

UIFont는 class인데 == 레퍼런스 카운트를 계산해야함 ㅎ



그럼 위에서 했던거랑 똑같이!!! 일단 label1, label2에 대한 Stack메모리 공간을 할당합니다.

여기에는 레퍼런스를 저장하겠죠.

text, font둘다 Heap을 사용하니 label1을 생성했을 때는 위와같은 그림이 되겠죠.



label1을 label2에 넣을때는 위와같은 그림이 되겠죠.



컴파일러가 generate하는 코드는 위와같이 될거에요.

label1을 label2에 넣고 각 프로퍼티에 대해 retain을 해주면서 레퍼런스 카운트를 증가시키고, 각각 다 쓰면 각 프로퍼티들을 release해주겠죠.

ㅠㅠㅠ 프로퍼티들이 둘다 Heap에 저장되어서 다 2번씩 해줘야함ㅋ......;;;


충격적인 사실은...우리의 Label타입이 struct라는 사실입니다.......이럴거면 왜;.;;;;

나는 쫌 빨리 해볼라고, 또는 뭐 값이 복사되어도 ㄱㅊㄱㅊ해서 struct로 만든건데

(아 물론 위에서 label2의 text값을 변경했다고해서 label1의 text값이 변경되는건 아닙니다..?)

이렇게 오버헤드가 커져버리니 화나는 부분


실제로, struct안에 이렇게 레퍼런스가 있는 경우, 레퍼런스 수에 비례하여 레퍼런스 카운팅 오버헤드를 지불하게 되며, 

둘 이상의 레퍼런스가 있는경우, class보다 레퍼런스 카운팅 오버헤드가 더 많이 유지됩니다. 충격적이죠? class로 만드는게 더 낫다ㅓ니...




바로 이러한 상황;;


자 또다른 예제를 봅시다.



메세지의 첨부기능을 추상화한 Attachment라는 struct를 만들었어요.

fileURL은 첨부파일의 경로를 의미하고 uuid는 서로 다른 클라이언트에서 첨부파일을 인식할 수 있도록 임의로 생성된 식별자,

그리고 mimeType은 첨부파일이 JPG / PNG / GIF인지 등을 저장하는 프로퍼티입니다.


이니셜라이즈는 init?으로 failable 이니셜라이저네요. 만약 첨부한 파일이 지원안하는 형식이면 nil반환할거임;;이네요.

자.............우리가 위에서 봤듯이..지금...Attachment는 struct이지만....Heap에 저장될 애들이 일단 String이랑...



URL은 struct지만, 실제로 init시 String을 받으므로..모든 프로퍼티의 contents가 Heap에 저장되게 되겠네요...



"둘 이상의 레퍼런스가 있는경우, class보다 레퍼런스 카운팅 오버헤드가 더 많이 유지됩니다."........^_ㅠ...

자!!!!!!!!!!!!우리 이걸 개선시켜봐요.

어떻게 개선시킬 수 있을까..일단 uuid를 봅시다.



iOS6부터, UUID라는 struct가 생겼는데요, 고유하게 식별하는데 쓰기 딱 좋은 struct라고 합니다. 무작위로 생성된 128비트 식별되어있고, uuid가 String이면 내가 uuid로 "Zedd"이런거 넣으면 또 어떡할거;;;;;; 이 UUID는 128비트 식별자를 struct에 "직접"저장하기 때문에 == Heap 할당 필요없음 정말 좋조ㅛ!!! 안전하기도 하구요.

ㅇㅋ

이제  mimeType을 보겠음



자, 이니셜라이저 처음에서 내가 지원안하는 형식이면 nil날거임ㅎ이라고 해줬죠? 

그걸 검사하는 것을 String을 Extension해서 해줬네요. 

오른쪽 코드를 조금 더 개선 시킬 수 있지않을까요??..



바로 이렇게 말이죠. enum은 struct와 마찬가지로 값타입이며 case들을 Heap에 저장하지 않습니다.

enum은 기본적으로 failable 이니셜라이저라...우리가 원하는 것을 걍 enum을 사용해서 똑같이 할 수 있죠!!!!


와 그럼 지금 uuid랑 mimeType이 원래 Heap에 메모리 할당이 필요했는데;;;;; 없어졌음;;;



이제 fileURL하나만 Heap할당이 필요해졌네요 :)

아무튼 이렇게 코드 개선을 하면 Attachment가 struct인 이유가 있는거죠!!!!!!!!!!


그리고 마지막으로 method dispatch로 넘어가봅시다.

이 내용은 

 Swift) dynamic이란? / Realm dynamic var?


-  OptimizationTips

를 읽고오시면 이해가 더 잘될수도 있고....아니면 이 글을 읽고 읽으시면 위 두 글이 더 이해가 잘 가실 수 있어요!!!!! 참고하세요..XD


런타임에 메소드를 호출하면, Swift는 정확한 구현을 실행해야합니다. 컴파일타임에 어떤 구현을 실행하도록 결정할 수 있다면, 이를 static dispatch라고 합니다.

컴파일러가 실제로 어떤 구현이 실행될건지 알기때문이죠. 즉, inline과 같은것을 포함하여 코드를 적극적으로 최적화 할 수 있게 됩니다.

이러한 static dispatch는 dynamic dispatch와는 대조적인데요,

dynamic dispatch는 컴파일러가 컴파일타임에 어떤 구현을 실행할건지 결정 할 수 없습니다.

런타임때만!!!! 실제로 구현된 곳으로 jump하게 되죠(jump to it. jump라는 말이 너무 찰떡이라 굳이 번역하지 않았..)

따라서 dynamic dispatch의 비용은 static dispatch보다 훨씬 큽니다.



스레드 동기화 오버헤드나, 레퍼런스 카운팅 및 Heap할당은 없었다고 해도, 이 dynamic dispatch는 컴파일러의 가시성(visibility)을 차단하므로 최적화를 막기됩니다.


자, 예제 코드를 한번 보도록 하죠.



Point가 struct로 구현되어있죠? 그리고 Point인스턴스를 만들고, drawAPoint함수를 호출하고, 그 함수 안에서 Point struct안에 구현되어있는 draw()라는 메소드를 호출합니다.

drawAPoint와 draw메소드 둘다 static dispatch되므로. == 컴파일러가 정확히 어떤 구현이 실행될것인지를 정확하게 알고있으므로 

최적화를 수행 할 수 있게됩니다!



바로 이렇게 말이죠.

어차피;;;drawAPoint에서 draw를 호출하니까 drawAPoint를 호출하는게 걍 draw()를 호출하는거잖아요? 

그래서 drawAPoint호출을 draw호출로 대체할 수 있게 됩니다.

== 실제 구현으로 대체 할 수 있음.



draw()도 호출할 필요없이, 그냥 draw()의 구현을 바로 실행할 수도 있게되죠. 


static dispatch가 dynamic dispatch보다 빠른 이유는,  single dynamic dispatch에 비해 single static dispatch와 비교해서 큰 차이는 없지만, static dispatch의 체인은 컴파일러가 전체 체인을 통해 가시성을 갖게 됩니다. 반면 dynamic dispatch 체인은 매 단계마다 추론을 하지 않고 상위 level에서 차단됩니다.

컴파일러는 호출 스택 오버헤드가 없는 단일 구현처럼 static method dispatch를 축소할 수 있습니다. 

구구절절 이야기하지만 바로 위 그림을 이야기 하는겁니다...


자 그럼 왜 dynamic dispatch를 왜??쓰는걸까요?

그 이유 중 하나는 다형성(polymorphism)과 같은 정말 강력한 것들이 가능하다는 것이죠.



자, Drawable클래스를 만들고 Point와 Line이  Drawable를 상속받도록 해주었어요.



그리고 drawables라는 배열을 만들었죠. Drawable은 class이고, 배열안에서는 이들에 대한 참조를 저장하기 때문에 모두 같은 크기일거에요.



그리고 얘네는 실제로 Heap에 있는 것들을 가리키겠고, Heap에서는 위에서 말했다시피 refCount를 자체적으로 가지고 있을 것입니다.



자, for문을 돌면서 이 drawables배열안의 인스턴스에 대해 draw()를 호출합니다. 

컴파일러가 여기서 정확히 어떤 구현을 실행해야하는지, 즉 Line의 draw를 실행해야하는지, Point의 draw를 실행해야하는지..이것을 컴파일러가 컴파일 타임에 어떤 구현이야!!!!라고 알 수 없는 것을 직관적으로 이해 할 수 있죠.

그럼 컴파일러는 어떤 draw를 호출해야하는지 어떻게 결정할까요?

컴파일러는 그 class타입 정보에 대한 것을 정적 메모리에 저장하고, 실제로 draw를 호출 할때, 컴파일러가 실제로 생성하는것은 타입 및 정적 메모리의 virtual method table이라고 하는 것(==vtable)을 조회합니다. 그리고 실행하기에 적합한 draw메소드를 찾고, 그러고 나서 파라미터로 실제 인스턴스를 전달합니다.




자, 결과적으로 우리는 class는 기본적으로 dynamic dispatch를 합니다. 자체적으로 큰 차이를 만들진 않지만, 메소드 체인, 인라인과 같은 최적화를 막을 수 있습니다.

하지만 모든 class가 이 dynamic dispatch를 사용 할 필요는 없죠.

class를 서브클래싱하지 않으면 final로 명시하면 됩니다. 컴파일러는 이를 보고 static dispatch를 하게 될것입니다. 

또한, 컴파일러가 앱에서 class를 서브클래싱 하지 않을 것이라는것을 추론하고 입증 할 수 있다면, 기회를 보고 dynamic dispatch를 대신하여 static dispatch를 하게 됩니다. (OptimizationTips에서 봤던..private랑 fileprivate명시해주는 걸듯)



자!!!! 이렇게 여러 기준에 따른 Swift의 성능을 봤는데요.

정리하면




각각 이러한 성능이 나온다고 하네요 :)


자, 우리가 오늘 알아야 할 것은, 

우리가 코드를 작성 할 때마다


1. 이 인스턴스가 Stack / Heap 중 어디에 할당될까?

2. 이 인스턴스를 전달 할때, 오버헤드를 포함하는 레퍼런스가 얼마나 많을까?

3. 이 인스턴스로 메소드를 호출하면, static / dynamic dispatch중 어떤게 될까?

우리가 이 dynamism을 필요하지 않는데도 비용을 지불하고 있다면, 우리의 성능은 의문의 1패를 하게 되는것이죠.


이런 생각들을 하라는 것입니다. 

이 WWDC영상의 반을 보았는데요, 반은 나중에 공부해야ㅈ ㅣ~~~

아무튼 이걸 보고 OptimizationTips를 보면 더 이해가 잘 갈 것 같네요 :)

모두들 연휴 잘 보내시고!!!!!!!!!!! 푹 쉬시길 바랍니다.

그리고 


"Swift에서 value타입과 reference타입의 주요 차이점은 값타입은 NSArray내부에 포함할 수 없다는 것입니다."(A key distinction is that value types cannot be included inside an NSArray)" < 아시는 분 ㅠㅠ 완벽하게 왜그런지 설명 할 수 있으시다면...환영...(NSArray가 클래스인건 압니다)

제일 궁금한건 Swift에서 NSArray안에 value타입(struct, enum, tuple)다 담기는데, 왜 안된다고 하는건지? 앞에 In Swift, ~~라고 말해서 더 헷갈zzz

아니면 제가 아예 잘못이해/접근 하고 있는건지..알려주시면 좋겠어요!!! inside an NSArray니까 NSArray의 메모리 구조에서 어떻게 되는건가? class니까 Heap할당이 될테고..뭐지!!?!??! ?_?


다음 편 보기






반응형