Is there a difference in performance computationally and/or memory-wise between Swift Array
/Dictionary
primitive functions removeAll
and init
? Basically, I am asking, what are the pros and cons to resetting a mutable collection in Swift, and is one considered the recommended way to empty an Array
/Dictionary
?
// Scenario A
var myArray = ["one", "two", "three"]
myArray.removeAll()
// Scenario B
myArray = ["one", "two", "three"]
myArray = []
The performance difference should not be significant as they do largely the same thing. Let’s look at 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())
}
}
There are several other implementations of this method for different types, but the pattern is always the same. If removeAll
parameter keepCapacity
is false
, just reinitialize it, largely equivalent to saying myArray = []
.
So, the only question is whether you want to preserve the capacity of the array after removing its elements or not (which you might do if you’re emptying a large array and about to repopulate with another array of the same size).
If you want, benchmark it. For example, add "Unit Test” target to your project, bumping up the number of iterations high enough to make the duration observable:
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)
}
}
With the following results:
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).
By the way, if you’re wondering about why I’m adding the totals after emptying the array, I’m just trying to make sure that I actually use the array after emptying it to ensure that the optimizer doesn’t optimize out the code doing the emptying. It’s not necessary in this case, but is prudent.
I also did the test with Int
instead of String
, as I’m not curious about the String
overhead, but rather trying to focus on the Array
behavior.
Bottom line, the performance difference was largely indistinguishable.
As stated in the docs removeAll()
is a Complexity: O(n), where n is the length of the array.
So it would vary in speed based on the size of the array. As others mentioned though it would be premature optimization to pick one over the other at the expense of readability.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With