Swift의 Type과 메모리 저장 공간
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 Type
은 shared instance 를 사용합니다. 따라서 두번째 변수에 할당된 instance 의 값을 변경하면 기존 값도 같이 변경되는 것이죠.
우리는 이 차이점에 따라 어떤 type을 사용할지 결정합니다. 이때 Reference type
보다 Value type
을 선택하는 주된 이유 중 하나는 성능 외에도, 의도치 않은 데이터의 변경을 방지할 수 있기 때문입니다. Value type
은 복사된 unique 한 instance를 얻을 수 있기 때문에, 다른 부분에서 해당 데이터를 변경하지 않는다는 것을 보장받을 수 있습니다. 변경하려고 하면 새로운 unique instance가 copy 된 후 이것이 변경 될 것입니다.
이는 특히 다른 스레드에서 데이터를 변경할 수 있는 다중 스레드 환경에서 유용합니다. 즉, Thread 간 의도하지 않은 공유로부터 안전합니다. (다중 스레드 환경에서 동일한 데이터를 변경하는 것은 디버그하기 굉장히 힘든 상황을 야기할 수 있습니다.)
Type에 따른 메모리 할당 공간
우리는 보통 각 Type 에 따른 메모리 할당 공간을 아래와 같이 생각했습니다. (저만 그랬나요? 🤔 )
Value Type
값 👉🏻Stack
에 메모리 할당Reference Type
값 👉🏻Heap
에 메모리 할당
하지만 Value Type
이 Heap
에, Reference Type
이 Stack
에 저장될 때가 있습니다. 즉 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
에 또 다른 문자열을 할당하면 스택 영역 주소는 변화가 없고, 내부 문자열을 저장하는 힙 영역 주소만 변경 됨. (클래스 래퍼런스 포인터가 동작하는 구조와 비슷)str2
에str1
을 할당하면, 다른 스택 영역 주소를 사용하되, 힙 영역은str1
의 힙 영역 주소와 동일한 주소 사용.str2
에 다른 문자열을 할당하면, 다시 새로운 힙 영역 주소가 사용 (copy-on-write)str2
에 “zzzz” 처럼 짧은 문자열을 넣을 경우 스택 공간에도 직접 저장 (긴 문자열은 스택 공간에 저장되지 않고 힙 영역을 그대로 사용)- String 타입의
MemoryLayout
이 16바이트라서 15글자까지만 스택 영역에 직접 저장
Array
- 스택 영역에 값이 생기지만, 읽어보면 그냥 8바이트에 곧바로 힙 영역 주소가 들어가 있음. 그래서
struct
타입이지만 참조가 1개만 있는 래퍼런스처럼 동작. array2
에array1
을 할당하면 서로 다른 스택 영역 포인터 변수에 같은 힙 영역을 저장하고 있다가, 어느 한쪽이 값이 바뀌면 힙 영역에 새로운 공간을 사용. (값 타입이라서 매번 힙 영역에 새로운 공간을 차지)
주요 참고 사이트
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://swiftrocks.com/memory-management-and-performance-of-value-types
https://developer.apple.com/forums/thread/50357
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