iyOmSd/Title: iOS Think🤔

[iOS] Swift Memory - COW (Copy On Write)

냄수 2020. 8. 3. 15:10
반응형

COW

처음 들어보는 분들도 있을거에요

정의를 먼저 말하자면

 

수정(쓰기)가 일어날때 복사한다 라는 뜻이에요

스위프트의 CollectionType인 Array, Dictionary, Set에 적용되는 개념이에요

 

참조를 통해서 불필요한 복사를 줄여서

메모리를 좀 더 아껴쓸 수 있어요

 

 

예시로 알아볼까요??

let a = [1...30]
let b = a

이런 코드가 있어요

그럼 일반적으로 생각해보면

a도 30개의 배열을 할당 받고

b도 30개의 배열을 할당 받을 거에요 그쵸??

 

배열 인덱스당 하나의 메모리를 할당 받아요

자료구조 배울 때 배열 처음[0]과 다음[1]인덱스의 주소값이 달랐던게 새록새록 기억나시나요..?

인덱스하나당 메모리를 할당받아요

 

만약 1000개, 10000개, 100000....0개 라면???

똑같은 배열을 한번 더 할당해줘야해요

너무 비효율적이죠..

 

Cow는 이러한 작업을 효율적으로 관리해주는 개념이에요

그림으로 보자면

 

 

 

Cow를 모를때는 아래 그림같이 생각했을거에요

 

Cow를 알게된후에는 이렇게 생각날거에요!!

바로 위의 그림과같이 a와 b가 동일한 메모리를 가르키고있어요

a와 b의 메모리주소는 같아요

아직 b가 메모리를 할당받지않고 같은 변수만 가르키는거에요!!

 

CollectionType은 ValueType 이잖아요.. 참조를해요...??!

 

헷갈릴 수 있어요 좀 더 생각해볼까요..?!

 

참조타입인 A가 있다고하면 

let a = A()
let b = a

class A {}

인경우에는

a는 메모리를 할당받고 A()인스턴스를 만들고 가지고있죠

b는 메모리를 할당받고 a의 인스턴스 주소를 가지고 있어요

이때 b의 메모리 주소는 a와 달라요

 

제가 하고싶은말은

변수가 참조하고있는 주소와  할당받는 메모리 주소를 헷갈리지말라는....

아래에서 코드와 함께 더 설명해볼게요

 

 

Cow를 코드로 테스트해볼게요

 

우선 기본타입부터 해볼까요~~

Int, String

    var a = 1
    var b = a
    address(o: &a) //0x7ffeefbff460
    address(o: &b) //0x7ffeefbff458

    var c = "1"
    var d = c
    address(o: &c) //0x7ffeefbff448
    address(o: &d) //0x7ffeefbff438

받은 메모리 주소값이 다르네요!!

적용이 되지않는다는걸 알 수 있죠

 

Struct, Class

    struct A {
        var a: Int
    }
    var sa1 = A(a: 1)
    var sa2 = sa1
    address(o1: &sa1) //0x7ffeefbff458
    address(o1: &sa2) //0x7ffeefbff450
    
    sa1.a = 2
    address(o1: &sa1) //0x7ffeefbff458
    address(o1: &sa2) //0x7ffeefbff450
    
    
    class B {
        var a = 3
    }
    
    var cb1 = B()
    var cb2 = cb1
    
    address(o2: &cb1) //0x7ffeefbff450
    address(o2: &cb2) //0x7ffeefbff448

Struct와 Class타입도 마찬가지로 적용이되지않아요

 

Class타입에보면 cb1, cb2가 다른 주소값이에요

참조를 하고있으니까 같아야하는거 아닌가요?!?!

 

위에서 말했던게 이거에요

이건 참조를 하고있는 주소를 나타내는게아니라

메모리의 주소를 나타내는거에요

각 변수는 따라서 새로운 메모리를 할당받고

그 변수가 같은 인스턴스를 가르키고 있는거에요

 

 

Array

    var arr1 = [1,2,3]
    var arr2 = arr1
    
    // CASE1::::::::::::::::::::::::
    address(o1: &arr1) //0x100517000
    address(o1: &arr2) //0x100517000
    arr2.append(4)
    // arr2가 변경
    address(o1: &arr1) //0x100517000
    address(o1: &arr2) //0x102b04330
    
    // CASE2::::::::::::::::::::::::
    address(o1: &arr1) //0x10053d5a0
    address(o1: &arr2) //0x10053d5a0
    arr1.append(3)
    // arr1가 변경
    address(o1: &arr1) //0x10053d760
    address(o1: &arr2) //0x10053d5a0
    
    // CASE3::::::::::::::::::::::::
    address(o1: &arr1) //0x100458c70
    address(o1: &arr2) //0x100458c70
    arr1.append(3)
    arr2.append(3)
    // arr1가 변경
    // 먼저 수정되는 쪽의 주소값이 변경됨
    address(o1: &arr1) //0x100458e30
    address(o1: &arr2) //0x100458c70

각변수가 같은 주소값을 가지고 있다가

한쪽을 다른 값으로 수정하면

그 변수에 메모리를 새로 할당해주면서 주소값이 바뀌는게 보이시나요?

 

스위프트에서는 이런식으로 불필요한 복사를 줄여서

성능을 보다 더 좋게 해줘요

 

 

 

주소값을 구하는 함수를 알려드려야 또 찾아가야하는 번거로움을 줄여드릴게요,, ㅎㅎ 

func address(o1: UnsafeRawPointer) {
    let address = String(format: "%p", Int(bitPattern: o1))
    print(address)
}

func address<T: AnyObject>(o2: UnsafePointer<T>) {
    let address = String(format: "%p", Int(bitPattern: o2))
    print(o2.pointee) // 변수가 가르키는 타입
    print(address) // 주소값
}

//pointer: UnsafePointer<T>
withUnsafePointer(to: &T) { pointer in
    pointer.pointee // 변수가 가르키는 타입
    pointer // 주소값
}

UnsafePointer: 변수에 대한 위치와 타입정보를 가지고있음

UnsafeRawPointer: 저장된 값이 어떤 타입인지에 대한 정보가 없음, 즉 주소값만 가져올 수 있고 변수가 어떤 타입을 가지고있는지모름

 

반응형