써니쿠키의 IOS 개발일기

[swift] Copy On Write (COW) 본문

swift, Ios

[swift] Copy On Write (COW)

sunnyCookie 2023. 3. 6. 16:59

안녕하세요 써니쿠키입니다🍪


오늘은 swift에서 뿐아니라 모든 프로그래밍에서 쓰이는 기술 중 하나인

Copy On Write(COW) 개념과 커스텀 Struct에 COW를 적용해보는 시간을 갖겠습니다

COW하면 소밖에 안떠올랐지만 이제 아~ Copy On Write~ 해야합니다 👻

 


  COW  

Copy On Write는 번역하면 쓰기 시 복사죠.

개념을 알고보면 아 말그대로 쓰기 시 복사구나! 하실겁니다


말 그대로 복사를해도 쓰기 시에 즉, 변경이 있을 때 복사하겠다는 겁니다.

 

무슨말이냐면,,

데이터를 '복사'할 때, 메모리에상에선 실제로 값이 복사되지 않고,

동일한 메모리를 주소를 참조하다가
데이터가 변경되면! 그제서야 메모리를 복사해서 변경할 값을 변경하는 기법입니다.

 

예를들어, Swift에서는 Array 타입이 Struct로 값타입인데요.
값타입은 복사하면, Stack메모리에 값들이 전부 복사되어서 쌓인다고 배웠을 겁니다.

과연그럴까요..! 조금있다 코드예시에서 확인해봅시다!

 

가끔 프로그래밍을 하다보면 복사만하고 변경할 필요가 없을 때도 있습니다.
이럴 때는 모두 복사되어 메모리에 저장되면 똑같은 데이터를 2배의 메모리를 써가며 저장하고 있겠다는건데 딱봐도 비효율적이죠!

이럴 때 COW를 적용하면,

메모리상에선 복사없이 참조를 하고있다가

변경이 생기면 그제야 메모리를 복사합니다.

그리고 복사한 메모리를 수정합니다!

이렇게 수정이 있을 때 진짜 복사를해서 메모리를 효율적으 사용하는 기법이 COW입니다.

 


  코드로 확인하기  

let array1: [Int] = [1, 2, 3, 4]
var array2 = array1

array2에 array1을 대입합니다


array는 struct로 값타입입니다.
값타입이니까 array2는 메모리상에서 array1 메모리를 복사한 새로운 메모리에 올렸을 것 같지만
Swift의 Array에는 COW가 적용되어있어있기 때문에 array2가 array1의 메모리를 참조하고있습니다.

 

정말 그런지 주소를 직접 확인해보겠습니다.

// 메모리 주소확인 함수
func adress(_ object: UnsafeRawPointer) -> String {
    let address = Int(bitPattern: object)
    return NSString(format: "%p", address) as String
}

// array1, array2 메모리 주소 출력
print(adress(array1))
print(adress(array2))

출력값을 확인하니 정말로 같은 메모리를 참조하고 있습니다.

 

그러면 "변경이 생기면 그제서야 진짜 복사를 한다" 고 했으므로
array2 값을 수정하고 주소를 찍어보겠습니다

//array2 값 변경
array2[0] = 0

// array1, array2 메모리 주소 출력
print(adress(array1))
print(adress(array2))

실제로 array2의 메모리주소가 바뀌었습니다.

그림으로 보면 아래와같이 수정된거죠!

 

array2에 array1을 복사해놔도, 같은 메모리를 참조하다가 값이 변경되서야 실제 복사가 됐습니다.

이렇게 '복사'할 때, 변경이 있기 전까지는 참조를 하다가 변경이 생기면 그제서야 메모리를 복사하고 수정하는 게 COW 기법입니다.

 


  Swift에서의 COW  

swift에서는 어디에 COW가 구현되어있을까요?


원시타입 구조체인 Int, Double, String에 구현되어있고

컬렉션 구조체인 Array, Set, Dictionary 등

COW가 구현되어 있습니다.

 

(그리고 얼마전에 WWDC에서보니 프로토콜 Existenstial Container의

value buffer에도 COW를 적용되어있다고 소개하는 걸 봤습니다)

 

참고로 사용자 정의 구조체(Struct)에는 COW가 구현되어있지 않습니다!
(=> 필요하다면 커스텀해서 구현해줄 수 있습니다.)


  사용자 정의 Struct에 COW 구현하기  

위에서 struct로 만드는 사용자 정의 구조체에는 COW가 구현되어있지 않다고 했습니다.

근데 필요하면 직접 커스텀으로 구현해 줄 수 있습니다.

 

일단 COW가 안되어있음을 먼저 확인하기 위해 Sunny라는 구조체를 만들고 복사 후 주소를 찍어보죠

struct Sunny {
    var name: String = "sunny"
    var height: Int = 163
}

var sunny1 = Sunny()
var sunny2 = sunny1

print(adress(&sunny1))
print(adress(&sunny2))

COW가 적용이 안되어있어 복사 시 바로 복사가 된 메모리를 갖고있습니다.

 

그럼 이제 Sunny 구조체에 COW를 적용하는 코드를 작성해보겠습니다.

 

대입 하자마자 바로 프로퍼티 복사가 되는것을 막기 위해

참조타입인 Class를 이용해 Sunny구조체를 wrapping 해줍니다.
(Sunny말고도 다른 타입에 대해 범용적으로 활용할 수 있도록 Generic타입을 사용합니다)

class DataWrapper<T> {
    var data: T

    init(data: T) {
      self.data = data
    }
}

Sunny 구조체를 제어해줄 CowData 구조체를 선언합니다.

struct CowData<T> {
    // Data Wrapper
    private var dataWrapper: DataWrapper<T>

    init(data: T) {
        self.dataWrapper = DataWrapper(data: data)
    }

    var data: T {
        get {
            return self.dataWrapper.data
        }
        set {
            if !isKnownUniquelyReferenced(&self.dataWrapper) {
                self.dataWrapper = DataWrapper(data: newValue)
            } else {
                self.dataWrapper.data = newValue
            }
        }
    }
}

여기선, 내부에서 class인스턴스를 저장프로퍼티로 가지면서,

그 인스턴스의 프로퍼티로 원본 데이터를 저장합니다.

그리고 data setter에서

dataWrapper에 대한 참조가 Unique하지 않으면 새로운 Wrapper를 생성하여 값을 대입해주고

dataWrapper에 대한 참조가 Unique하면 기존 Wrapper에 대해서 struct 값을 대입해줍니다.

 

사용은 아래처럼 할 수 있습니다.

var cowData = CowData(data: Sunny())

 


이렇게 오늘은 COW의 개념을 배우고 실제 코드로확인해보고

커스텀 Struct에 COW를 적용하는 코드에 대해 알아봤습니다..!

 

Swift에서는 컬렉션타입들에 대해서 COW가 적용되어있지만,

흔히 사용하는 사용자 정의 구조체 (Struct)엔 적용이 안된다는 점을 새로 배울 수 있었습니다.

 

이제 COW  = "쓰기 시 복사" 가 확 와닿으실거라 믿습니다.

 


참고

Copy on write in Swift || iOS interview Questions

무과장 [Swift] class와 struct 그리고 Copy On Write

반응형
Comments