관리 메뉴

사과하는 제라스

[Swift 지식] removeAll(keepingCapacity: Bool)의 성능 비교 본문

제라스의 iOS 공부/Swift 지식

[Swift 지식] removeAll(keepingCapacity: Bool)의 성능 비교

Xerath(제라스) 2024. 3. 14. 22:04

목차

    728x90
    반응형

    서론

    안녕하세요~ 개발자 제라스입니다👋🏻🤖👋🏻

    제가 요즘 HackingWithSwift로 SwiftUI를 학습하면서 아주 기초적인 것부터 학습 중인데요.

    Array 타입에서 removeAll을 쓰는 과정에서 removeAll(keepingCapacity: Bool) 이란 옵션이 있더라구요!

     

    평소같으면 그런가보다하고 넘어갔겠지만..!

    또 갑자기 쓸 데 없는 집착이 생겨버렸습니다 ㅠㅠㅠ

    그래서 이번 기회에 심심하게나마 한번 그 효과랑 성능에 대해 얘기해보고자 합니다.(생각보다 큰 효과는 없었지만...그래도 읽어주소 😭😭)

     

    그럼 시작해보겠습니다.

    Array의 removeAll 옵션 - keepingCapacity

    저는 removeAll에 이런 옵션이 있는지도 몰랐습니다ㄷㄷㄷ

    의역하면 '용량을 유지하면서 싹 다 삭제?'정도가 되겠네요.

    이 옵션은 default값이 false라서 그냥 다음과 같이

    var a = Array(1...1000)
    a.removeAll() // == a.removeAll(keepingCapacity: true)
    print(a.count) // 0
    print(a.capacity) // 0

    이렇게 하면 그저 배열도 사라지고 메모리에서 사라지기도 하죠.

     

    하.지.만

    var a = Array(1...1000)
    a.removeAll(keepingCapacity: false)
    print(a.count) // 0
    print(a.capacity) // 1000

    이렇게 쓰면 용량은 유지, 즉, 메모리에서 해제되지 않은 채 그 용량 그대로 남는 겁니다.

    그래서 removeAll 이후 count는 0이, a.capacity는 1000이 나오는 겁니다.

     

    그래서 '그럼 이렇게 유지하면 뭐가 좋나?' 생각이 들어 찾아봤는데,

    어떤 사람은 반드시 유지하면서 쓰는게 좋다는 둥의 얘기를 했다고 하더라구요?

    https://www.hackingwithswift.com/forums/100-days-of-swift/why-do-we-use-array-removeall-keepingcapacity-true-all-the-time/17937

     

    HackingWithSwift에서 나눈 질의응답을 본 결과, Apple이 default로 false로 둔 이유는 메모리적인 측면에서 굳이 삭제 후 들고 있을 필요가 없고, 그게 더 일반적으로는 안전하기 때문이라고 합니다.

    하지만, 만약 배열을 비우고 다시 새 값들을 채워넣을 때는 오히려 이렇게 capacity가 변하지 않는 것이 더 성능이 좋다고 합니다.

     

    간단히 생각해봐도 새로 계속 값을 추가해서 용량의 변화를 내는 것보다는 용량은 유지한 채 넣는게 메모리 작업이 덜 들겠죠?ㅎㅎ

    keepingCapacity 값의 차이에 따른 refill 속도

    일단 배열의 길이가 짧을 때는 keepingCapacity 여부에 상관없이 속도는 둘 중 랜덤으로 빠르다고 나오더라구요.

    언제는 false인게 빠르고 언제는 true인게 빠르고 했습니다.

     

    그래서 HackingWithSwift의 Paul 아저씨의 조언에 따르면 큰 용량(10 million이라고 했지만 전 50000정도만 했습니다 ㅎㅎ)일 때 그 효과가 나타난다길래...

    40.97GB는 선넘었지...

    이렇게나 메모리 차지를 하고 스왑핑까지 해대면서 실행해본 결과

     

    keeping capacity가 더 빠르게 채워지는 효과를 보았습니다.

    5차례 수행해도 똑같은 결과가 나왔는데 이정도면 효과가 있지 않나 싶네요ㅎㅎ

    코드는 다음과 같습니다.

    import UIKit
    
    func keeping() -> Double {
        var arr = Array(1...50000)
        let startTime = CFAbsoluteTimeGetCurrent()
        arr.removeAll(keepingCapacity: true)
        for i in 0..<50000 {
            arr.append(i)
        }
        let duration = CFAbsoluteTimeGetCurrent() - startTime
        return duration
    }
    
    func nonKeeping() -> Double {
        var arr = Array(1...50000)
        let startTime = CFAbsoluteTimeGetCurrent()
        arr.removeAll(keepingCapacity: false)
        for i in 0..<50000 {
            arr.append(i)
        }
        let duration = CFAbsoluteTimeGetCurrent() - startTime
        return duration
    }
    
    let keepingDuration = keeping()
    let nonKeepingDuration = nonKeeping()
    
    print("keepingCapacity: true일 때:", keepingDuration)
    print("keepingCapacity: false일 때:", nonKeepingDuration)
    
    if keepingDuration < nonKeepingDuration {
        print("Keeping capacity가 빠릅니다.")
    } else if keepingDuration > nonKeepingDuration {
        print("Not keeping capacity가 빠릅니다.")
    } else {
        print("둘 속도가 같습니다.")
    }

     

    removeAll()의 시간복잡도가 O(n)???

    사실 생각해보면

    var a = Array(1...100)
    a = []

    이렇게 해버리면 시간복잡도가 O(1)입니다.

    상당히 빠르죠.

    반면, removeAll()해버리면 배열을 순회하면서 하나씩 다 지웁니다.(이건 keepingCapacity와 상관없이요!)

    공식문서에도 O(n)이라고 나와있죠.

    그럼 removeAll을 쓸 필요가 없네!!?? 굳이??

    맞습니다. 비울 때는 그럴 필요가 없지만 removeAll은 대신 여러가지 옵션들이 있죠. keepingCapacity, where 등등...

    그래서 상황에 따라 오히려 removeAll이 필요한 경우가 있습니다.

     

    결론, 둘 다 필요하다. 하지만 평상 시에 배열을 비우는 경우엔 []가 시간복잡도 면에선 훨씬 나이스하긴 하다.

    마무리

    오늘은 조금 가벼운 주제로 진행해보았는데 이렇게 성능 테스트하면서 막연한 생각이 실제로 보여지니 재밌더라구요 ㅋㅋㅋ

    원래 뭐 배우는 것보다 써보는게 재미지죠😅😅

    껄껄...

    아무튼 이렇게 용량을 유지하는 법도 있지만 반면 이걸 쓰는게 무조건적으로 좋은 것은 아닙니다.

    메모리 차지를 그대로 하기 때문이죠...

    그래서 정말 배열을 비우고 다시 그만큼 즉시 채우는 것이 아니라면 남용하는 것이 좋은 것만은 아닌 듯합니다ㅎㅎ

     

    오늘도 포스팅 끝까지 읽어주셔서 감사합니다!

    다음에도 좋은 포스팅으로 돌아오겠습니다 :-D

     

     

    추가 학습 내용 - capacity는 2배씩 늘어난다

    이건 포스팅을 한지 5일이 지난 지금 추가해서 학습하게 된 내용을 작성해봅니다!

     

    아니... 오늘 SwiftUI 공부를 좀 더 하다보니까 길이가 3짜리인 배열을 removeAll(keepingCapacity: true)를 하고 나니까

    남은 capacity가 4더라구요!!!!!!!

     

    엥!?!?!?!?!

     

    근데 몇번을 해도 4인 걸 보고는 왜 그러지???하고 찾아보니 공식문서에서도 

     

    'The new storage is a multiple of the old storage’s size.'

    즉, 배열에 요소를 추가하게 되는데 만약 가득 차면 그 다음 용량은 원래 것의 2배를 채우는 방식입니다.

     

    예를 들어 어떤 배열에 총 5개의 객체를 추가하는 코드를 봅시다.

    class User {
        let id: Int
    
        init(id: Int) {
            self.id = id
        }
    
        deinit {
        	print("Bye Bye")
        }
    }
    
    var users = [User]()
    
    for i in 1...5 {
        let user = User(id: i)
        users.append(user)
    }
    
    print(users.count)
    users.removeAll(keepingCapacity: true) 
    print(users.capacity)

     

    이렇게 5개의 User 객체가 users 배열에 append 된다고 합시다.

    일단, 결과부터 보면 다음과 같습니다.

    5
    Bye Bye
    Bye Bye
    Bye Bye
    Bye Bye
    Bye Bye
    8

     

     

    왜 count는 5였는데 capacity가 8일까요?

     

    간편하게 user1부터 user5까지 넣는 과정이라고 합시다.

     

    user1이 들어가면 capacity는 0에서 1이 됩니다.

    근데 이제 꽉찼죠?

    그래서 user2가 들어갈 때 capacity는 1의 2배인 2가 됩니다.

    근데 이제 또 꽉찼죠?

    그래서 user3이 들어갈 때 capacity는 2의 2배인 4가 됩니다.

    그 다음 user4를 넣습니다.

    근데 또 꽉찼죠?

    그래서 마지막으로 user5가 들어갈 때 capacity는 4의 2배인 8이 됩니다.

     

    정말 간단하죠? 넣으려는데 꽉차 있으면 capacity를 2배로 늘리는 거에요!!

     

    이건 몰랐는데...!! 실제로 테스트해보고서 우연히 발견해서 학습할 수 있었네요 ㅋㅋㅋ 👍🏻👍🏻

    참고

    https://www.hackingwithswift.com/forums/100-days-of-swift/why-do-we-use-array-removeall-keepingcapacity-true-all-the-time/17937

    https://swift-it-world.tistory.com/16

     

    Swift 실행 속도 확인하기

    안녕하세요. iOS 앱을 만들다 보면 서버 통신으로 API를 이용하거나 반복문을 사용할 때 시간이 얼마나 걸렸는지 실행 속도를 쉽게 측정하는 방법을 알아보겠습니다. func checkTime() { let startTime = CFA

    swift-it-world.tistory.com

    https://developer.apple.com/documentation/swift/array/removeall(keepingcapacity:)-1er5

     

    removeAll(keepingCapacity:) | Apple Developer Documentation

    Removes all elements from the array.

    developer.apple.com

    https://developer.apple.com/forums/thread/702923

     

    Swift Array removeAll() performanc… | Apple Developer Forums

    Using Swift Array I found removeAll() to be very slow and not perform well. Especially the larger the array (the more objects it contains), the slower it was. Is there a way to achieve good performance while performing the same function as removeAll() of a

    developer.apple.com


    https://developer.apple.com/documentation/swift/array/capacity

     

    capacity | Apple Developer Documentation

    The total number of elements that the array can contain without allocating new storage.

    developer.apple.com

     

    https://stackoverflow.com/questions/27830470/swift-array-capacity-vs-array-count

     

    Swift array.capacity vs array.count

    I understand the array.count ( the number of elements in the array ). count is useful to iterate over the array's elements. I sort of get the gist of array.capacity capacity An integer value that

    stackoverflow.com

     


    아직 꼬꼬마 개발자입니다.

    더 나은 설명이나 코드가 있다면 언제든 환영입니다.

    적극적인 조언과 피드백 부탁드립니다!

     

    그럼 오늘도 개발 가득한 하루되세요!

    - Xerath -

    🤖🤖🤖🤖🤖🤖🤖

     

    728x90
    반응형