View

300x250

이번에 야곰 iOS 강의를 들으면서 Array와 ContiguousArray 의 차이에 대해 설명을 들었다.

결론만 요약을 하면

  • Array는 obj-c 의 NSArray 와 브릿징될 수 있기 때문에, 이에 대비해 준비를 해둔다.
  • ContiguousArray는 브릿징 X → 이를 준비하지 않기에 성능상으로 유리하다.

따라서, 가능한 경우에 ContiguousArray를 사용하는게 좋다!


여기 내용을 들으면서 ContiguousArray 라는 것도 처음 알게되었고, objc와 swift 간의 브릿징(형변환)을 염두에 두고 내부적으로 관리를 하고 있다는 것도 처음 알게되었다. 그래서 요런 부분에 대해 조금 더 살펴보고 기록으로 남겨두려고 한다!

The Swift Array Design

라는 스위프트의 Array 내부 구현을 설명해준 깃허브 문서를 찾았다.

https://github.com/apple/swift/blob/main/docs/Arrays.md

위 문서에 따르면 Array의 구현에서 아래 2가지에 집중했다고 한다.

  • 코코아 프레임워크 → swift → 코코아 프레임워크 의 빠른 변환을 위해 NSArray, Array 간의 casting이 메모리 추가 할당 없이 가능해야함
  • stack 처럼 동작하기 위해, push와 pop이 O(1) 에 가능해야함

그리고 Swift에서는 크게 3가지의 Array 방식을 사용하고 있다. Array, ContiguousArray, 그리고 ArraySlice.

  • Contiguous Array
    • 가장 간단한 Array 형태. 메모리에 연속으로 데이터가 저장된다.
  • Array
    • Swift의 기본 Array 형태.
    • value type인 경우에는 Contiguous Array 처럼 연속으로 저장될 수 있으나, reference type인 경우 NSArray에 저장되어 불연속적으로 저장될 수 있다. 🫢🫢
  • SliceArray
    • 배열의 일부 구간.
    • (NSArray 처럼) 만약 배열이 메모리에 연속으로 저장되어있지 않은 경우, 구간을 가져올 때 O(1) 보다 시간이 더 걸릴 수 있다.

일단 여기까지는 오케이. 조금 더 검색을 하다보니, Apple Swift 레포지토리에서 해당 코드 연결들을 확인해볼 수 있다고 되어있어 찾아봤다.

Swift의 Array, ContiguousArray 내부 구조

스위프트의 배열은 기본적으로 “같은 타입의 element 들이 메모리의 연속적인 공간에 저장된 것”이다. 이 덕분에, 특정 element에 접근하려 할 때 $O(1)$ 의 시간이 걸린다.

대략적인 구조를 파악해보려고 깃허브에 있는 Swift 구현 코드를 타고타고 내려가봤다. 정확하게 파악을 하진 못했지만, 대략적으로 아래의 구조로 구성되어 있는 것 같았다. SwiftArrayBodyStorage 에 데이터가 저장되도록 구현되어있는 것 같았는데, 저 아래가 어떻게 구성되었는지는 숨겨져있는 것 같다.

 

이렇게 문서 타고 들어가는거 오랜만에 해봤는데, 역시나 재밌다… ㅋㅋㅋ


Array 방식과 ContiguousArray 방식의 차이는 중간에 Storage를 BridgeStorage가 감싸고 있는지, 감싸지 않고 있는지였다. 그 아래는 ContiguousArrayStorage를 동일하게 사용하고 있는 것으로 보였다. 아래 이미지처럼, ArrayBuffer 에서도 ContiguousArrayStorage를 감싸서 storage로 사용하는 것을 볼 수 있다.


BridgeStorage로 감싸줬기 때문에, 데이터를 처리할 때 BridgeObject를 거치면서 한 단계의 과정이 더 필요하게 되고, 이로 인해 성능의 저하가 발생할 수 있다는 것으로 이해했다!

그러면 objc 타입의 NSArray와 기본 Array (Contiguous Array)가 어떻게 다르기에 요런 변환 과정이 필요한지를 찾아보았다.

Objc NSArray VS Swift Array

NSArray는 obj-c에서 사용하는 배열이다. 특징은 타입에 상관 없이 모든 원소를 하나의 NS 배열에 담을 수 있다.

이 이유는 참조타입만 배열에 담기 때문이다! 언어의 특성상, 객체지향으로 모든 것을 처리하기 때문에, NSArray에는 값이 아닌 객체만 (객체를 가리키는 포인터만) 담을 수 있다고 한다. 그래서 만약 3 이라는 값을 담고싶다면 NSNumber(3) 처럼 클래스를 거쳐 객체로 만들어야 담을 수 있다.

값 타입의 Array는 그래서 기본적으로 NSArray로 넘어갈 수 없다.

Swift의 Array는 이와 달리 값 타입과 참조 타입 등 모든 타입의 값을 담을 수 있으나, 하나의 배열에는 하나의 타입만 담을 수 있다. (Any Array를 사용하면 모든 타입을 담을 수는 있겠지만, 모두 Any 라는 타입이므로 여기에 포함된다)

Array에 값 타입을 담으면 ContiguousArray와 성능이 차이가 없다는 말이 여기에서 온거구나 무릎을 쳤다.

약간 이 지점에서 점점 구조가 이해가 되기 시작했다..!

결론은

야곰의 iOS 강의에서 최적화를 위해 알려준 내용들 중에 다음의 내용이 있었다.

  1. Array 에 값 타입을 이용하면 NSArray로 브릿징할 필요가 없기 때문에 컴파일러가 알아서 최적화를 해준다.
  2. Array에 참조 타입을 이용하면 컴파일러는 NSArray로 브릿징할 준비를 한다 … 여기에서 성능 하락 가능 ⇒ 이때는 ContiguousArray를 사용하면 NSArray로 브릿징이 불가능하기 때문에 컴파일러가 최적화를 해준다.
  3. 그러므로, 참조 타입을 배열에 담는데 obj-c API나 프레임워크를 사용하지 않을 거라면 ContiguousArray를 사용하자.

야곰 강의를 보면서 이해가 안되는 부분들에 대해 수많은 억까와 문서와 깃허브 속에서 정보를 찾고 깨달아냈는데, 다시 야곰 강의로 돌아오니 너무 명쾌하게 설명이 되어있었다. ㅋㅋㅋㅋ 약간 대학교 강의 PPT가 이해가 안돼서 전공책보고 돌아왔더니 PPT가 책 내용 중에 필요한 것만 깔끔하게 정리되어있는 요약본으로 보이는 느낌. 역시 나의 부족함이 문제였던 것 같다.

아직 해결 못한 궁금한 점

Array의 내부 구현을 파봤을 때는 BridgeObject를 통해 NSArray로 갈수도 있었고 ContiguousArrayStorage로 갈수도 있었다. 값 타입을 저장하면 ContiguousArrayStorage로 저장되기 때문에 ContiguousArray랑 동일한 성능을 낸다고 이해했다.

그러면 참조 타입을 저장하면 어떻게 저장이 되는거지? ContiguousArrayStorage에 포인터가 들어가고, 이를 NSArray로 변환할 때 포인터를 담은 배열로서 NSArray로 변환을 시키는 것인지, 아니면 내부적으로 NSArray의 형태로 포인터를 저장하는 것인지는 아직 파악하지 못했고, 이해를 잘 못한 것 같다.

써놓고 나니깐 ContiguousArrayStorage 에 저장하는 것 같기는 한데, Array가 항상 메모리에 연속적으로 데이터를 저장하지는 않을 수 있다는 내용과 NSArray의 instance로 저장될 수 있다는 내용을 서치하면서 읽은 것 같아서 감이 잘 안오는 것 같다.

샤라웃

https://github.com/apple/swift/blob/main/docs/Arrays.md

https://github.com/apple/swift/blob/main/stdlib/public/core/ArrayBody.swift#L22

https://elisha0103.tistory.com/19

https://medium.com/@hientrq/bridging-between-swift-and-objective-c-why-should-we-care-e46e2873b6db

http://ankit.im/swift/2016/01/07/exploring-swift-array-implementation/

https://green1229.tistory.com/321

320x100
Share Link
reply
반응형
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30