【问题标题】:Performance: Array.removeAll vs `= []`性能:Array.removeAll vs `= []`
【发布时间】:2019-01-10 16:30:27
【问题描述】:

Swift Array/Dictionary 原始函数 removeAllinit 在计算和/或内存方面的性能是否存在差异?基本上,我在问,在 Swift 中重置可变集合的优缺点是什么,是否被认为是清空 Array/Dictionary 的推荐方法?

// Scenario A
var myArray = ["one", "two", "three"]
myArray.removeAll()

// Scenario B
myArray = ["one", "two", "three"]
myArray = []

【问题讨论】:

  • 您是否真的在使用其中一个或另一个时遇到了性能问题,或者这只是出于好奇?创建一个测试应用程序并测试性能。确保您的测试使用在真实设备上运行的优化版本运行。
  • @maddy 只是好奇,并不完全是运行和执行我自己的性能测试的专家

标签: swift performance memory collections


【解决方案1】:

性能差异不应该是显着的,因为它们在很大程度上做同样的事情。来看看the source code

/// Removes all elements from the array.
///
/// - Parameter keepCapacity: Pass `true` to keep the existing capacity of
///   the array after removing its elements. The default value is
///   `false`.
///
/// - Complexity: O(*n*), where *n* is the length of the array.
@inlinable
public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) {
  if !keepCapacity {
    _buffer = _Buffer()
  }
  else {
    self.replaceSubrange(indices, with: EmptyCollection())
  }
}

several other implementations of this method 用于不同的类型,但模式始终相同。如果removeAll参数keepCapacityfalse,重新初始化就好了,很大程度上相当于说myArray = []

所以,唯一的问题是您是否要在删除其元素后保留数组的容量(如果您要清空一个大数组并准备用另一个相同大小的数组重新填充,您可能会这样做) .


如果您愿意,可以对其进行基准测试。例如,将“单元测试”目标添加到您的项目中,将迭代次数提高到足以使持续时间可观察到:

class MyAppTests: XCTestCase {

    func testPerformanceRemoveAll() {
        var countTotal = 0
        var myArray: [Int] = []

        self.measure {
            for _ in 0 ..< 1_000_000 {
                myArray = Array(repeating: 0, count: 1_000)
                myArray.removeAll(keepingCapacity: false)
                countTotal += myArray.count
            }
        }

        XCTAssertEqual(countTotal, 0)
    }

    func testPerformanceReinitialize() {
        var countTotal = 0
        var myArray: [Int] = []

        self.measure {
            for _ in 0 ..< 1_000_000 {
                myArray = Array(repeating: 0, count: 1_000)
                myArray = []
                countTotal += myArray.count
            }
        }

        XCTAssertEqual(countTotal, 0)
    }

}

结果如下:

Test Case '-[MyAppTests.MyAppTests testPerformanceReinitialize]' started.
/.../MyApp/MyAppTests/MyAppTests.swift:41: Test Case '-[MyAppTests.MyAppTests testPerformanceReinitialize]' measured [Time, seconds] average: 0.221, relative standard deviation: 6.559%, values: [0.264467, 0.216076, 0.216146, 0.216040, 0.216014, 0.216426, 0.216374, 0.215876, 0.216272, 0.216152], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100
Test Case '-[MyAppTests.MyAppTests testPerformanceReinitialize]' passed (2.646 seconds).

Test Case '-[MyAppTests.MyAppTests testPerformanceRemoveAll]' started.
/.../MyApp/MyAppTests/MyAppTests.swift:26: Test Case '-[MyAppTests.MyAppTests testPerformanceRemoveAll]' measured [Time, seconds] average: 0.235, relative standard deviation: 6.712%, values: [0.282223, 0.229732, 0.229601, 0.229624, 0.229584, 0.229652, 0.229695, 0.229729, 0.229702, 0.229659], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100
Test Case '-[MyAppTests.MyAppTests testPerformanceRemoveAll]' passed (2.602 seconds).

顺便说一句,如果您想知道为什么我在清空数组后添加总数,我只是想确保在清空数组后我实际使用了数组以确保优化器不会优化清空代码。在这种情况下没有必要,但要谨慎。

我还使用Int 而不是String 进行了测试,因为我对String 开销并不好奇,而是试图关注Array 的行为。

归根结底,性能差异在很大程度上无法区分。

【讨论】:

    【解决方案2】:

    如文档removeAll()中所述

    是一个复杂度:O(n),其中 n 是数组的长度。

    因此它的速度会根据数组的大小而有所不同。正如其他人所提到的,尽管以牺牲可读性为代价来选择一个而不是另一个是为时过早的优化。

    【讨论】:

    • 顺便说一下,这告诉我们removeAll 的性能如何根据数组的大小而变化,但它并没有告诉我们removeAllinit 的性能。
    猜你喜欢
    • 2012-03-17
    • 2013-08-25
    • 2021-02-27
    • 2013-10-18
    • 1970-01-01
    • 1970-01-01
    • 2018-02-21
    • 2013-01-31
    • 1970-01-01
    相关资源
    最近更新 更多