서론
Swift의 Array는 이미 훌륭하지만 더 최적화를 시킬 수 있는 방법이 있다는 것을 알고 계시나요?
오늘 알아볼 ContiguousArray와 ArraySlice를 이용해 최적화할 수 있는 상황이 있습니다.
오늘은 그 상황과 방법을 알아보도록 합시다.
ContiguousArray
ContiguousArray는 항상 배열 요소를 인접한 메모리 영역에 저장합니다.
쉽게 말해 항상 연속적인 저장을 보장 받습니다.
그건 배열도 똑같은거 아니야? 라고 생각할 수 있겠지만
Swift의 Array<Element>는 Element가 클래스 타입이거나 @objc 프로토콜 타입인 경우 메모리에 연속적으로 저장되지 않습니다.
왜냐하면 NSArray 저장소에 백업이 될 가능성이 있기 때문입니다.
그래서 Array<Element>는 Element가 클래스 타입이거나 @objc프로토콜 타입인 경우 연속적으로 저장이 안 된다고 합니다.
NSArray에 브릿징하지 않거나 Objective-C API에 배열을 전달할 필요가 없는 경우
ContiguousArray를 사용해서 항상 연속적인 저장을 보장 받을 수 있습니다.
인접한 메모리에 저장한다는 의미는 시간 효율이 더 좋다는 것을 의미합니다.
시간 측정을 해보면서 정말 그런지 확인해 봅시다.
Array vs ContiguousArray 시간 비교
테스트는 http://online.swiftplayground.run 에서 진행했습니다.
public func measureTime(_ closure: () -> ()) -> TimeInterval {
let startDate = Date()
closure()
return Date().timeIntervalSince(startDate)
}
시간 측정 코드입니다.
class Address {
var street = ""
var city = ""
var state = "";
}
var array: Array<Address> = []
var contiguousArray: ContiguousArray<Address> = []
배열을 연속 저장하지 않게 하기 위해서 클래스 타입을 원소로 설정했습니다.
Array와 ContiguousArray에 append 하는 시간을 비교해 봅시다.
Append 비교
let item = Address()
let arrayTime = measureTime {
for _ in 0..<100000 {
array.append(item)
}
}
let contiguousArrayTime = measureTime {
for _ in 0..<100000 {
contiguousArray.append(item)
}
}
print("----- Append -----")
print("arrayTime: \(arrayTime)")
print("contiguousArrayTime: \(contiguousArrayTime)")
//----- Append -----
//arrayTime: 0.0027309656143188477
//contiguousArrayTime: 0.0022710561752319336
Append 시간 비교입니다.
사실 기대만큼 차이가 나지는 않네요???
테스트할 때 참고한 글은 5배가 더 차이가 났는데 흠;;;
그래도 contiguousArray가 더 빠르긴 합니다.
크기를 1억, 10억으로도 다시 테스트 해보았는데요.
1억 반복할 때는 array는 45초, contiguousArray는 41초가 소요되었고
10억 반복할 때는 array는 453초, contiguousArray는 437초가 소요되었습니다.
append의 시간 차이가 반복 횟수와 비례하므로 의미는 크지 않은 것 같습니다.
그래서 왜 차이가 많이 안 날까? 찾아 봤는데 결론은 아무런 정보가 없었습니다...
내부 구현 코드도 살펴봤는데 달라진 이유를 모르겠고
새벽까지 찾아봤지만 아무런 소득이 없었네요 ㅠㅠ
혹시 아시는 분은 댓글로 알려주세요.
흥미로웠던건 array와 contiguousArray의 시간 비교에 대해 토론의 장을 벌인 이 사이트인데요.
https://forums.swift.org/t/execution-time-contiguousarray-vs-array/12467/24
이쪽은 심지어 ContiguousArray가 두 배 더 느린 상황입니다 ㅎㅎ;;;
의견이 분분한 토론 주제 같습니다.
배열 반복 시간 비교
append 비교 결과에서 절망한 뒤 곰곰히 생각해 봤는데요.
연속적으로 저장되는 것과 아닌 것의 차이니까 배열 반복 부분에서는 큰 차이가 있지 않을까? 생각이 들었습니다.
그래서 한 번 비교해봤어요!
결론부터 말하면 for-in을 통해 원소에 접근했을 때 기대하던 결과를 볼 수 있었습니다!!
let item = Address()
var array: Array<Address> = Array(repeating: item, count: 100000000)
var contiguousArray: ContiguousArray<Address> = ContiguousArray(repeating: item, count: 100000000)
1억 개의 클래스 타입 원소를 가진 배열을 선언했습니다.
let arrayTime = measureTime {
for i in array {
}
}
let contiguousArrayTime = measureTime {
for i in contiguousArray {
}
}
위 코드처럼 for-in을 이용해 array와 contiguousArray의 원소를 가져오는 수행 시간을 비교해봤는데요.
arrayTime: 11.805001974105835
contiguousArrayTime: 5.237215042114258
Array가 두 배의 시간이 더 걸린 것을 볼 수 있었습니다.
for-in 뿐만 아니라 map을 포함한 다른 고차함수도 마찬가지였어요.
이렇게 array보다 contiguousArray가 효율적이라는 것은 입증이 되었습니다.
ArraySlice
ArraySlice란?
Array, ContiguousArray 또는 ArraySlice 인스턴스의 조각입니다.
let firstHalf = absences[..<midpoint]
let secondHalf = absences[midpoint...]
이런 문법 익숙하시죠??
배열을 slice한다고 하고 firstHalf, secondHalf가 바로 ArraySlice 타입입니다.
ContiguousArray와 마찬가지로 배열 저장 시 인접 메모리 공간을 사용하고
Objective-C와의 브릿징 지원하지 않습니다.
ArraySlice를 생성하더라도 자체적으로 새로운 저장 영역을 할당하지 않습니다.
대신 원래 배열의 저장 공간에 대한 참조를 제공합니다.
ArraySlice 주의할 점
ArraySlice를 사용할 때 주의할 점도 있는데요.
첫 번째로, 원본 배열의 일부로서 존재하기 때문에 원본 배열이 사라지면 ArraySlice도 접근할 수 없습니다.
두 번째로, 원본 배열을 참조하기 때문에 원래 배열을 다 쓰더라도 ArraySlice가 남아 있으면 메모리 해제가 되지 않습니다.
이러한 이유들로 ArraySlice를 길게 사용하는건 권장하지 않습니다.
ArraySlice 사용하는 이유
ArraySlice를 사용하는 이유는 메모리 효율 때문입니다.
배열의 일부만 필요할 때가 있습니다.
예를 들면 배열을 절반으로 나눴을 때, 앞 부분의 합과 뒷 부분의 합을 비교하고 싶을 때요.
ArraySlice가 없다면 앞부분을 다른 배열에 넣고 뒷부분을 또 다른 배열에 넣어서 구해야 할 것입니다.
배열의 크기가 작다면 다행이지만 10만, 100만씩 크다면 메모리 낭비겠죠??
ArraySlice는 따로 새로운 공간을 만들지 않고 기존 배열을 참조해서 사용하기 때문에
비교적 메모리 효율이 좋습니다.
ArraySlice는 익숙하게 사용되는 타입이므로 따로 실습 코드는 넣지 않았습니다!
이런 상황에서 필요하다는 것을 인지하시고
필요하실 때 배열을 slicing 하여 사용하시면 좋을 것 같습니다.
감사합니다!
참고
https://developer.apple.com/documentation/swift/array
https://developer.apple.com/documentation/swift/contiguousarray
https://zeddios.tistory.com/m/600
https://medium.com/@nitingeorge_39047/swift-array-vs-contiguousarray-a6153098a5
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.