Swift의 Type과 메모리 저장 공간

Value Type이 Heap에 할당 될 때가 있다구여???

naljin
10 min readMar 7, 2021

Swift의 Type

Swift의 Type 은 크게 두 카테고리로 나뉩니다.

1. Value Type

  • struct, enum, tuple
  • Swift의 기본 자료형 타입 (Int, Float, Double, Bool, String, Array, Dictionary, Set 등은 모두 struct 형태로 구현)

2. Reference Type

  • class, function, closure

이 두가지의 차이점은 뭘까요? 바로 Copying 의 동작에 차이가 있습니다.

Value Type 은 copying시 data의 unique한 복사본을 생성합니다. 반면 Reference Typeshared instance 를 사용합니다. 따라서 두번째 변수에 할당된 instance 의 값을 변경하면 기존 값도 같이 변경되는 것이죠.

우리는 이 차이점에 따라 어떤 type을 사용할지 결정합니다. 이때 Reference type보다 Value type을 선택하는 주된 이유 중 하나는 성능 외에도, 의도치 않은 데이터의 변경을 방지할 수 있기 때문입니다. Value type복사된 unique 한 instance를 얻을 수 있기 때문에, 다른 부분에서 해당 데이터를 변경하지 않는다는 것을 보장받을 수 있습니다. 변경하려고 하면 새로운 unique instance가 copy 된 후 이것이 변경 될 것입니다.

이는 특히 다른 스레드에서 데이터를 변경할 수 있는 다중 스레드 환경에서 유용합니다. 즉, Thread 간 의도하지 않은 공유로부터 안전합니다. (다중 스레드 환경에서 동일한 데이터를 변경하는 것은 디버그하기 굉장히 힘든 상황을 야기할 수 있습니다.)

Type에 따른 메모리 할당 공간

우리는 보통 각 Type 에 따른 메모리 할당 공간을 아래와 같이 생각했습니다. (저만 그랬나요? 🤔 )

  • Value Type 값 👉🏻 Stack에 메모리 할당
  • Reference Type 값 👉🏻 Heap에 메모리 할당

하지만 Value TypeHeap에, Reference TypeStack에 저장될 때가 있습니다. 즉 Type에 따른 저장 공간이 상황에 따라 달라질 수 있다는 것입니다.

머라고요???

네, 바로 제가 글을 쓰게 된 이유이죠!! 아래에서 해당 상황들을 살펴봅시다.

1. Value Type 값 👉🏻 Heap에 메모리 할당

protocol 을 corform 할 때, generic 을 사용할 때 등 다양한 상황(참고)이 있지만, 이번 시간에 설명할 것은 Collection에 대한 얘기입니다.

Array, Dictionary, Set, String(collection of characters)과 같은 가변 길이 Collection들은 일반적으로 내부 데이터를 Heap 에 저장해서 사용합니다. 컴파일 타임사이즈를 정확히 알기 어렵기 때문Heap에 할당 후, 적절히 데이터 / 사이즈를 증감시킵니다. (주요 출처 1 / 주요 출처 2)

정말 데이터를 reference해서 사용하고 있는 것인지 나름대로 파악해 보기 위해 MemoryLayout의 size 함수를 이용해 보았습니다. 해당 함수를 통해 String 구조체의 크기를 살펴보면 16 byte 를 차지하는 것을 알 수 있습니다.

let stringSize = MemoryLayout<String>.size
print(stringSize)//16

그럼 String 값이 길어질 때 해당 인스턴스의 size 는 달라질까요? 이를 확인해 보기 위해 아래와 같은 코드를 작성했습니다.

var shortString: String = "a"
var longString: String = String(repeating: "abc", count: 100000)

print(MemoryLayout.size(ofValue: shortString)) //16
print(MemoryLayout.size(ofValue: longString)) //16

longString에 꽤 긴 String 값을 할당했음에도 불구하고 같은 size 를 출력하는 것을 확인할 수 있습니다. 흠.. 왜일까요? size(ofValue:) 함수의 설명으로 돌아가 봅시다.

주어진 instance 의 memory footprint 를 리턴하고, pointer나 class 인스턴스에 대해서는 reference data의 size 와 상관없이 동일한 memory footprint 를 return 한다.

아하..! 위의 설명을 통해 String 변수에 어떤 값을 담든 같은 사이즈 값을 리턴하는 것으로 보아, reference data 를 가리키고 있구나 유추할 수 있었습니다.

물론 상황에 따라 값 자체가 스택에 저장되기도 합니다.

이곳에서 Small strings can by spilling to the stack 라고 말하거나, 이곳에서 Collections allocate their storage on the heap, although the compiler is able to optimize them to stack allocations in some cases 라고 언급된 것 처럼 말이죠.

한편 Array, String 등은 기본적으로 struct로 구현된 Value Type이기 때문에 인스턴스마다 unique 한 데이터를 가져야합니다. 하지만 매번 새로운 공간을 할당하고 복사하는 것은 부담이 있고, 특히 데이터가 커질 수록 부담은 커질 것입니다. 이러한 문제를 해결하기 위해 collection은 copy-on-write (cow)라는 최적화 기법을 사용합니다.

copy on write 기법이란, 값을 새로운 변수에 할당할 때 바로 복사본을 만드는 것이 아니라, 수정(write)이 발생할 때 복사본(copy)을 만드는 것입니다. 수정 전까지는 기존 element가 저장된 메모리 주소를 참조하는 방식으로, 변수간 같은 instance를 공유합니다.

2. Reference Type 값 👉🏻 Stack에 메모리 할당

Reference Type의 사이즈가 고정되어있거나, 라이프타임을 예측할 수 있을 때 스위프트 컴파일러는 Reference type을 Stack에 할당할 수도 있다고 합니다. 이러한 최적화는 SIL 생성 구간에서 발생합니다. 더 자세한 내용은 이곳을 참고하세요.

추가

위의 글에서는 String과 Array 등 값 타입의 메모리 할당이 어떻게 되는지 분석합니다. 간단히 정리하자면 아래와 같지만, 직접 읽어보는 것을 추천드립니다.

String

  • str1에 “abcd”를 할당하면 스택 영역에 16바이트가 할당되고, 힙 영역MALLOC_TINY 영역에 내부 문자열이 공간이 또 잡힘.
  • str1또 다른 문자열을 할당하면 스택 영역 주소는 변화가 없고, 내부 문자열을 저장하는 힙 영역 주소만 변경 됨. (클래스 래퍼런스 포인터가 동작하는 구조와 비슷)
  • str2str1을 할당하면, 다른 스택 영역 주소를 사용하되, 힙 영역은 str1힙 영역 주소와 동일한 주소 사용.
  • str2다른 문자열을 할당하면, 다시 새로운 힙 영역 주소가 사용 (copy-on-write)
  • str2에 “zzzz” 처럼 짧은 문자열을 넣을 경우 스택 공간에도 직접 저장 (긴 문자열은 스택 공간에 저장되지 않고 힙 영역을 그대로 사용)
  • String 타입의 MemoryLayout이 16바이트라서 15글자까지만 스택 영역에 직접 저장

Array

  • 스택 영역에 값이 생기지만, 읽어보면 그냥 8바이트에 곧바로 힙 영역 주소가 들어가 있음. 그래서 struct 타입이지만 참조가 1개만 있는 래퍼런스처럼 동작.
  • array2array1을 할당하면 서로 다른 스택 영역 포인터 변수에 같은 힙 영역을 저장하고 있다가, 어느 한쪽이 값이 바뀌면 힙 영역에 새로운 공간을 사용. (값 타입이라서 매번 힙 영역에 새로운 공간을 차지)

주요 참고 사이트

https://developer.apple.com/swift/blog/?id=10

https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html

https://docs.swift.org/swift-book/LanguageGuide/Closures.html

https://academy.realm.io/kr/posts/letswift-swift-performance/

https://www.vadimbulavin.com/value-types-and-reference-types-in-swift/

https://www.freecodecamp.org/news/the-story-of-one-mother-two-sons-value-type-vs-reference-type-in-swift-6e125af2d5d0/

https://swiftrocks.com/memory-management-and-performance-of-value-types

https://developer.apple.com/forums/thread/50357

https://www.letmecompile.com/swift-struct-vs-class-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EB%B9%84%EA%B5%90-%EB%B6%84%EC%84%9D/

https://developer.apple.com/documentation/swift/memorylayout/2486283-size

기타 참고 사이트

https://developer.apple.com/videos/play/wwdc2016/416/

https://devmjun.github.io/archive/Swift-StructVSClass

https://zeddios.tistory.com/596?category=685736

https://www.reddit.com/r/swift/comments/68557w/is_swift_array_dictionary_stored_in_stack_instead/

https://developer.apple.com/documentation/swift/string

https://medium.com/commencis/stop-using-structs-e1be9a86376f

https://sesang06.tistory.com/176

https://link.medium.com/MmaE9z6Jpeb

https://academy.realm.io/posts/goto-mike-ash-exploring-swift-memory-layout/

https://singcodes.wordpress.com/2016/07/05/performancing-swift-3/

https://docs.swift.org/swift-book/LanguageGuide/CollectionTypes.html

--

--