Swift - Copy on Write
Swift에는 참조타입(클래스)와 값타입(구조체, 튜플, 열거형)이 있다. 이 중 값타입은 copy semantic 즉, 값 타입을 변수에 대입(assign)하거나 합수의 매개변수로 넘겨줄때 해당 값의 데이터가 모두 ‘복사’된다는 특징을 가진다. 이러한 경우 동일한 두 값을 서로 다른 메모리 주소에 갖고 있게 될 것이다.
What is this Copy-on-Write?
Swift에서 큰 값 타입의 데이터를 변수에 대입하거나 매개변수로 넘기게 되면 모든 복사된 데이터를 위한 메모리가 필요하므로 성능상 큰 문제가 발생할 수 있다. 이러한 문제를 최소화하기 위해서 Swift 표준 라이브러리는 배열과 같은 몇몇 값 타입에 대해 하나의 참조만 존재한다면 복사가 아니라 해당 참조 내에서 값 변경을 변경하는 메커니즘을 설계했다. 그래서 배열을 변수에 대입하거나 함수의 매개변수로 넘겨주는 것이 반드시 배열의 모든 데이터를 복사한다는 것을 의미하지 않는다.
즉, Copy-on-Write은 실제로 값을 복사하지 않고, 동일한 값을 참조하다가 데이터 변경이 발생될 시에 복사해 값을 변경하는 기법이다.
예제 코드
func print(address o: UnsafeRawPointer ) {
print(String(format: "%p", Int(bitPattern: o)))
}
var array1: [Int] = [0, 1, 2, 3]
var array2 = array1
// Print with just assign
print(address: array1) //0x600000078de0
print(address: array2) //0x600000078de0
// Let's mutate array2 to see what's
array2.append(4)
print(address: array2) //0x6000000aa100
//Output
//0x600000078de0 array1 address
//0x600000078de0 값 변경 전 array2의 주소
//0x6000000aa100 값 변경 후 array2의 주소
이 코드는 Copy-on-Write이 어떻게 작동하는지 보여주는 간단한 예이다. 코드를 살펴보면 먼저 array1을 생성하고 array2에 대입했다. 이 시점에 Copy-on-Write 때문에 값이 복사되 array1과 array2는 동일한 메모리 공간을 가리키고 있게 된다. 그리고 값의 변경(mutation)이 일어날 때 값이 복사된다.
Implementing Copy-on-Write behavior for your custom value types
Copy-on-Write behavior를 직접 구현할 수도 있다.
final class Ref<T> {
var val : T
init(_ v : T) {val = v}
}
struct Box<T> {
var ref: Ref<T>
init(_ x: T) { ref = Ref(x) }
var value: T {
get { return ref.val }
set {
if (!isUniquelyReferencedNonObjC(&ref)) {
ref = Ref(newValue)
return
}
ref.val = newValue
}
}
}
// This code was an example taken from the swift repo doc file OptimizationTips
// Link: https://github.com/apple/swift/blob/master/docs/OptimizationTips.rst#advice-use-copy-on-write-semantics-for-large-values
이 샘플 코드는 참조 타입을 사용하여 generic 값 타입 T에 대한 Copy-on-Write 을 어떻게 구현하는지 보여준다. 기본적으로 참조타입을 관리하고 하나의 참조만 있는 경우가 아니라면 새로운 인스턴스를 반환하는 wrapper이다. 하나의 참조만 있다면 새로운 인스턴스를 생성하지 않고 참조타입의 값을 변형(mutate)시킬 뿐이다.
Conclusion
Copy-on-Write은 Swift에서 매우 무거운(heavy) 연산인 값 타입의 복사를 최적화하는 아주 좋은 방법이다.