면접 중 받았던 질문 중 COW(Copy-On-Write) 에 대해서 질문을 받았었다. 물론 개념도 알고 왜 사용되는지 알지만, 바로 대답을 못했어서 아쉬웠다. 오늘 기록해보고 커스텀 타입에서 COW를 구현하는 방법에 대해서 알아보자.
Copy On Write는 Swift의 성능 향상을 위한 기술 중 하나이다. 구조체의 경우 클래스보다 성능이 여러 면에서 우의를 점하고 있는데, 딱 하나의 단점이 있다. 값 타입이기 때문에 항상 값 복사가 일어나면 메모리 공간 상에서 비효율적이라는 것이다.
그래서 Swift는 Array와 같은 Collection Type에서 Copy-On-Write를 제공한다.
Copy-On-Write 는 값타입임에도 초기에는 참조를 하고, 실제로 값이 변경되었을 때에 값 복사를 해주는 기술이다. 아래의 코드에서 확인해보자
func address(of object: UnsafeRawPointer) -> String{
let address = Int(bitPattern: object)
return String(format: "%p", address)
}
var firstArray: [Int] = [1,2,3]
var secondArray: [Int] = firstArray
print(address(of: &firstArray)) //0x10125bd80
print(address(of: &secondArray)) //0x10125bd80
값 복사가 일어나면 당연스럽게 메모리 주소가 달라야겠지만, 아직 값을 변경하지 않았으므로 참조가 일어나고 있어, 메모리 주소가 같은 것을 볼 수 있다.
secondArray = [4,5,6]
print(address(of: &firstArray)) //0x10125bd80
print(address(of: &secondArray)) //0x101105d00
그리고 설명처럼 값을 변경시켰을 때는? 이렇게 메모리 주소가 변경되는 것을 볼 수 있다!
그렇다면 내가 직접 만든 타입에서 COW를 통해 성능향상을 경험하고 싶다면? 어떻게 해야될까?
일단 커스텀 타입을 생성한다.
struct Coordinate {
var x: Int
var y: Int
}
다음으로는, 대입연산에 의해 즉각적인 복사가 일어나는 것을 막기위해 참조타입으로 Coordinate을 한번 Wrapping 해줘야 된다.
final class DataWrapper<T> {
var data: T
init(data: T) {
self.data = data
}
}
dataWrapper에 대한 참조가 Uniquely하지 않으면 새로운 Wrapper를 생성하여 값을 대입해줍니다.
struct CowData<T> {
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
}
}
}
}
아주 간단합니다. 실제 현업 코드에서 모든 Struct 타입들에 적용할지는 잘 모르겠지만, 만약 값 복사가 많이 발생한다면? 한 번 고려해볼만 사항인 것 같다!