Copy on write
Copy on write
swift 에서 값 타입 (struct, enum) 을 복사할 때는 항상 새로운 인스턴스가 할당된다고 알고 있다. 그러나 Array, Set, Dictionary 같은 collection type 은 내부적으로 아주 많은 원소를 가질 수도 있다. 만약 10000개의 원소를 가지고 있는 배열이 있는데, 이것을 새로운 프로퍼티에 할당하거나 매개변수로 넘길 때마다 새로운 인스턴스를 생성한다면, 성능이 떨어질 수 있다.
따라서 swift 뿐 아니라 많은 언어에서 Copy on write 방식을 사용해 값을 복사한다. 직역하자면 write, 즉 쓰기 작업을 할 때 값을 복사한다는 뜻으로, 할당 시 바로 복사하는 Copy on assign 방식과는 다르다. swift 에선 Array, Set, Dictionary 와 같이 Collection 타입을 사용할 때 COW 방식으로 복사가 이루어진다.
아래 코드를 통해 자세히 알아보자.
var arr: [Int] = [1,2,3,4]
var arr2: [Int] = arr
print(address(of: arr)) // 0x600001739ae0
print(address(of: arr2)) // 0x600001739ae0
swift 에서 Array 와 Int 는 모두 value type 으로, 그 자체의 정의에 따르면 두번째 줄에서 arr2 에 arr 를 할당한 순간 값의 복사가 일어나서 아예 새로운 인스턴스를 가지고 있는게 정상적이다. 그러나 arr 와 arr2 변수의 메모리 주소를 출력해보면 똑같은 주소값을 가지고 있는 것을 알 수 있다. 즉 COW 방식으로 복사를 하기 때문에, 단순히 새로운 변수에 값을 할당하는 것만으로는 실제 복사가 이루어지지 않는다!
이제 arr 에 append 로 원소를 추가해보자. 즉 쓰기 작업을 하는 것이다.
var arr: [Int] = [1,2,3,4]
var arr2: [Int] = arr
print(address(of: arr)) // 0x600001739ae0
print(address(of: arr2)) // 0x600001739ae0
arr.append(5)
print(address(of: arr)) // 0x600002604200
print(address(of: arr2)) // 0x600001739ae0
arr 에 append 를 해서 쓰기 작업을 했다. 그 결과로 쓰기 작업을 실행한 arr 의 주소값이 달라진 것을 볼 수 있는데, 비로소 값의 복사가 일어난 것이다.
이번엔 arr 말고 arr2 에 append 를 해보자.
var arr: [Int] = [1,2,3,4]
var arr2: [Int] = arr
print(address(of: arr)) // 0x600001739ae0
print(address(of: arr2)) // 0x600001739ae0
arr2.append(5)
print(address(of: arr)) // 0x600001739ae0
print(address(of: arr2)) // 0x6000026053a0
아까와 다르게 arr2 의 주소값이 바뀐 것을 볼 수 있는데, 여기서 먼저 쓰기 작업이 일어나는 변수에 새로운 인스턴스가 할당되는 것을 볼 수 있다.
Copy on write 의 장점
1000개의 원소를 가지고 있는 배열이 있다. 이 배열을 다른 변수에 할당할 때마다 값의 복사가 이루어진다면, 매번 복사가 이루어질 때마다 원소 1000개 만큼의 메모리를 추가적으로 소모하게 된다. 그러나 만약 값을 수정하지 않으면 굳이 값을 복사할 필요가 있을까? 단순히 똑같은 메모리 주소를 참조하게 만드는게 모든 값을 복사해서 새로운 인스턴스를 생성하는 것보다 훨씬 빠를 것이다.
복사한 값이 불변하다면 굳이 값을 복사할 필요가 없다. 어차피 똑같은 데이터를 가지고 있기 때문이다. 하지만 배열에 어떤 연산이 더해져서 두 개의 변수가 가지고 있을 데이터가 달라진다면, 이 때 비로소 값을 복사하면 되는 것이다.
즉 불필요한 복사를 줄임으로써 오버헤드를 줄이는 것이다.
Copy on assign
swift 의 collection 타입이 COW 방식으로 값을 복사한다고 하였는데, 그렇다면 일반 struct 타입은 정말 COA, 즉 값을 할당할 때 바로 복사가 일어날까? Int 를 복사해서 그 결과를 확인해보았다.
var value: Int = 3
var value2: Int = value
var value3: Int = value2
print(address(of: &value)) // 0x10000c0a0
print(address(of: &value2)) // 0x10000c0a8
print(address(of: &value3)) // 0x10000c0b0
일반 struct 타입은 COA 방식으로 값의 복사가 할당 시점에 바로 진행되는 것을 볼 수 있다.
References
[Swift] Value Type의 메모리 주소
Value Type은 cow를 한다면서 왜 Array만 동일 instance에 대해 같은 주소를 반환할까요?
sujinnaljin.medium.com
Swift) COW (Copy-on-Write)
안녕하세요 소들입니다 😞 오늘은 뭔가 축축 쳐지는 날이네요 며칠 째 계속되는 장마 탓도 있고.. (비 혐오자) 개발과는 참 애증의 관계인 것인지.. 공부할 건 너~~~~~~무 많고..ㅜㅜ 멋진 개발자
babbab2.tistory.com