티스토리 뷰

Swift

Swift ) ContiguousArray / ArraySlice

Zedd0202 2018. 9. 28. 18:15
반응형

안녕하세요 :) Zedd입니다.

오늘은 .. ContiguousArray / ArraySlice를 공부해봅시댜



ContiguousArray



- OptimizationTips

- The Swift Array Design

글에서도 말했듯이, ContiguousArray는 메모리에 연속적으로 저장되는 Array를 말해요. Array<Element>의 경우, Element의 타입이 class타입(또는 @objc프로토콜 타입)일 경우, NSArray저장소에 백업 될 수 있으므로 메모리에 연속적으로 저장되지 않아요. Element가  class타입(또는 @objc프로토콜 타입)이 아닐경우에는 연속적인 저장을 보장한다고 합니다.

이 글을 보시면. ContiguousArray와 Array의 성능을 비교해놓았는데요, 우리가 배열에 백만이나 되는.....걸 담을 일이 많이 없긴 하지만, 아무튼 2배이상 성능이 차이가 났다고 해요 :) 그래서 이러한 성능때문에 OptimizationTips에 나온 것 같네요.


ContiguousArray는 말그대로!!! 연속적으로 저장되는 배열입니다. 


ContiguousArray타입은 항상 element를 인접한 메모리 영역에 저장하는 특수한 배열입니다.

이는 element타입이 class또는 @objc프로토콜 인 경우, NSArray인스턴스 또는 메모리의 연속적인 영역에 element를 저장 할 수 있는 Array와 대조됩니다.

Array element타입이 클래스 또는 @objc프로토콜이고, NSArray에 배열을 브릿징하거나 Objective-C API에 배열을 전달 할 필요가 없는 경우, ContiguousArray를 사용하면 Array보다 더 효율적이고 예측가능한 성능을 얻을 수 있습니다. 배열의 Element타입이 구조체 또는 열거형인 경우, Array와 ContiguousArray는 비슷한 효율성을 가져야합니다. (잉 왜 should have similar efficiency라고 해놨지..........Array디자인에서는 동일하다고 했으면서;....)

배열 사용에 대한 자세한 내용은 대부분의 프로퍼티 및 메소드를 공유하는 Array및 ArraySlice를 참조하세요.



그렇다면 ArraySlice도 봅시다.



ArraySlice




(ArraySlice도 이전글에서 언급했지만, 항상 연속적으로 저장됩니다.)

ArraySlice의 정의를 보면, "Array, ContiguousArray 또는 ArraySlice 인스턴스의 slice입니다."

ArraySlice를 또 slice해도 ArraySlice라는 것!


ArraySlice타입을 사용하면, 더 큰 배열의 section에서 작업을 빠르고 호율적으로 수행 할 수 있습니다. 슬라이스 요소를 새 저장소로 복사하는 대신, ArraySlice인스턴스는 더 큰 배열의 저장소에 대한 view를 제공합니다. (ㅇ;ㅣ게 무슨소리지) 또한 ArraySlice는 Array와 동일한 인터페이스를 제공하므로, 일반적으로 슬라이스에서 (ArraySlice에서) 원래 Array와 동일한 작업을 수행 할 수 있습니다.


Slices Are Views onto Arrays


예를 들어,  이러한 absences배열이 있다고 생각해보죠.

각 수업에 들어오지 않은 학생들의 수를 저장한 배열입니다.


let absences = [0, 2, 0, 4, 0, 3, 1, 0]



수업의 전반부에서의 결석과, 후반부에서의 결석을 비교하기 원한다고 가정해보면, absences배열을 둘로 나눠서 시작하겠죠.


let absences = [0, 2, 0, 4, 0, 3, 1, 0]

let midpoint = absences.count / 2


let firstHalf = absences[..<midpoint]

let secondHalf = absences[midpoint...]


firstHalf와 secondHalf슬라이스는 자체적으로 새로운 저장 영역을 할당하지 않습니다. 

대신, 각각은 absences배열의 저장소의 view를 제공합니다.

absences배열에서 호출 할 수 있는 모든 메소드를 호출 할 수 있습니다. 

어느쪽이 더 많은 결석을 가지는지 알아보려면 reduce(_:_:) 메소드를 사용하여 각 합계를 계산해보면 되겠죠.


let firstHalfSum = firstHalf.reduce(0, +)

let secondHalfSum = secondHalf.reduce(0, +)


if firstHalfSum > secondHalfSum {

    print("More absences in the first half.")

} else {

    print("More absences in the second half.")

}


중요 : ArraySlice인스턴스의 장기보관은 권장하지 않습니다. slice는 원래 배열의 수명이 끝난 후에도 더 큰 배열의 전체 저장소에 대한 참조를 제공합니다. 따라서, slice의 장기간 저장은 더이상 접근 할 수 없는 요소의 수명을 연장시킬 수 있으며, 이는 메모리 및 객체 누출(object leakage)로 나타날 수 있습니다.


흠 계속 ArraySlice가 어디 다른곳, 그러니까 어떤 배열(A)에서부터 ArraySlice인스턴스를 "새로" 만들었다고 해도, 새로운 저장소를 할당하지 않고, 원래 배열(앞에서 어떤배열이라고 했던 A배열)의 저장소의 view를 제공한다고 그랬잖아요?

일단 다 이해가 가는데 저장소의 view를 제공한다고 한다는게 무슨뜻이냐..!!!! 

제가 생각하기에는, The Swift Arrray Design글에서, ArraySlice를 설명 할 때,



이러케 설명을 했잖아요? 지금은 첫번째 그림을 보면 되겠네요. (지금 Array로 만들었지만, 어차피 Int는 value Type일테고, The Swift Arrray Design글에서 value Type을 Array<Element>에 담았을 때는 ContiguousArray와 동일한 성능을 가진다고 했으니, 첫번째 그림은 ContiguousArray이지만 지금 상황에서는 Array도 봐도 될듯?)

그러니까 보시다시피, Slice가 새로운 저장소를 만드는게 아니라, 원래 배열에 대한 참조를 제공하는 것이죠. ㅇㅋ???

근데 우리의 Swift는 레퍼런스 카운트를 보고 메모리 해제를 하는데.....만약에 Slice가 이 원래 배열에 대한 레퍼런스를 가지고 이따 == 메모리 해제가 안된다 == 만약 내가 원래 배열을 다 썼어!! 근데도 메모리에 계속 남아있는 상황이 발생 할 수 있다는거임. ==> 그니까 ArraySlice는 장기보관 하지말았으면 하는 부분...ㅎ;;; 




이라고 말하는 것 같네요@! 

저는 이렇게 이해했는데..제 해석이 틀렸다면 댓글 달아주시길 바랍니다.


아무튼 계속 하겠음

view를 제공한다는 것이 무슨 느낌? 의미?인지 아시겠죠?


Slices Maintain Indices


Array 및 ContiguousArray와 달리 ArraySlice인스턴스의 시작 인덱스는 항상 0이 아닙니다.

Slice는 동일한 요소에 대해 더 큰 배열의 동일한 인덱스를 유지하므로, Slice의 시작 인덱스는 생성방법에 따라 다르므로, 전체 배열이나 Slice에서 인덱스 기반 연산을 수행 할 수 있습니다.

흠 이부분은 이해가 안가실 수도 있으니 잠깐 보도록 합시당



let
absences = [0, 2, 0, 4, 0, 3, 1, 0]

let midpoint = absences.count / 2


let firstHalf = absences[..<midpoint]

let secondHalf = absences[midpoint...]



우리가 ArraySlice인스턴스를 만들 때, 이런식으로 만들었었어요.

firstHalf의 첫번째 인덱스를 알고싶음


firstHalf[0] // 0 


쨘 0이당


그럼 


let firstHalf = absences[1..<midpoint]


absences[..<midpoint]가 아니라, 1부터 시작해서 slice해봅시다.


firstHalf[0] // Fatal error: Index out of bounds


아시겠죠? 

"ArraySlice인스턴스의 시작 인덱스는 항상 0이 아닙니다."


다음으로 고고

Collection과 subsequences간의 인덱스 공유는 Swift의 collection algorithms디자인에서 중요한 부분입니다. 

위 absences배열에서 결석이 있는 처음 이틀을 찾는 작업을 수행한다고 가정해봅니다.


1. firstIndex (where :)를 호출하여 absences 배열에서 0보다 큰 첫 번째 요소의 인덱스를 찾습니다.


2. 1단계에서 찾은 인덱스 다음부터 시작하여 absences배열의 slice를 만듭니다.


3. firstIndex(where:)를 다시 호출하고, 이 단계에서 생성된 slice를 호출합니다.


4. 1, 3단계에서 찾은 인덱스를 사용하여 결과를 print합니다.


if let i = absences.firstIndex(where: { $0 > 0 }) {                 // 1

    let absencesAfterFirst = absences[(i + 1)...]                   // 2

    if let j = absencesAfterFirst.firstIndex(where: { $0 > 0 }) {   // 3

        print("The first day with absences had \(absences[i]).")    // 4

        print("The second day with absences had \(absences[j]).")

    }

}

// Prints "The first day with absences had 2."

// Prints "The second day with absences had 4."


특히 눈여겨 봐야할 점은 자, i는 원래 배열에서 부터 나왔죠!?? absences.~~해서요. 근데 j같은 경우, 원래 배열에서 slice이 된 배열에서 나왔죠.

하지만 결국에서는 원래 absences배열의 값에 접근되는데 사용되었죠. 


Note : Slice의 시작 및 끝 인덱스를 안전하게 참조하려면, 항상 특정 값 대신(위에서 했던것 처럼 firstHalf[0] 이렇게 말고) startIndex 및 endIndex프로퍼티를 사용하세요.






이렇게 해서 ContiguousArray와 ArraySlice문서를 공부해보았습니다

새로 배운 점이 많아 뿌듯하네요 :)


도움이 되었길 바라며 즐거운 금요일 되세요 ~_~

반응형