性能:Array.removeAll vs `= []`:

13

在 Swift 中,Array/Dictionary 的基本功能 removeAllinit,在计算性能和/或内存方面是否有区别?基本上,我的问题是,重置可变集合的优缺点是什么,哪一种被认为是清空 Array/Dictionary 的推荐方式?

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

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

1
你是真的在使用其中一种时遇到了性能问题,还是只是出于好奇?创建一个测试应用程序并测试性能。确保你的测试是在优化构建上运行,并在真实设备上运行。 - rmaddy
2
@maddy 只是好奇,不是一个关于运行和进行自己的性能测试方面的专家。 - rolling_codes
2个回答

18

它们大体上执行相同的功能,因此性能差异不应该很大。让我们看一下源代码

/// 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())
  }
}

这个方法还有其他几种实现方式,但是模式总是相同的。如果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 的行为。

总之,性能差异在很大程度上是无法区分的。


1
如文档中所述,removeAll() 的复杂度为 O(n),其中 n 是数组的长度。
因此,其速度将根据数组的大小而变化。正如其他人所提到的,为了可读性而选择一个而不是另一个是过早优化。

3
顺便提一句,这告诉我们removeAll的性能如何随着数组大小的变化而变化,但它并没有告诉我们removeAllinit之间的性能差异。 - Rob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接