After seeing the "Reducing a sequence onto its own elements to simplify code and reduce errors" pitch, I was considering that it should have an inout
variant, and that got me to wondering why reduce(into:)
didn't have an additional variant that mutated its initial value.
I found @dabrahams' comment from the original review:
But I ran some naive benchmarks and found that this kind of code can still require copies depending on the value being accumulated.
func testRawAccumulation() {
let ys = Array(repeating: 1, count: 1_000_000)
var xs: [Int] = []
xs.reserveCapacity(5_000_000)
measure {
for y in ys { xs.append(y) }
for y in ys { xs.append(y) }
for y in ys { xs.append(y) }
for y in ys { xs.append(y) }
for y in ys { xs.append(y) }
}
}
func testReduceInto() {
let ys = Array(repeating: 1, count: 1_000_000)
var xs: [Int] = []
xs.reserveCapacity(5_000_000)
measure {
xs = ys.reduce(into: xs) { xs, y in xs.append(y) }
xs = ys.reduce(into: xs) { xs, y in xs.append(y) }
xs = ys.reduce(into: xs) { xs, y in xs.append(y) }
xs = ys.reduce(into: xs) { xs, y in xs.append(y) }
xs = ys.reduce(into: xs) { xs, y in xs.append(y) }
}
}
The testReduceInto
runs about 10x slower in an optimized build than testRawAccumulation
.
This overload, however, performs the same as testRawAccumulation
:
extension Sequence {
@inlinable
public func reduce<Result>(
into result: inout Result,
_ updateAccumulatingResult: (inout Result, Element) throws -> Void
) rethrows {
for element in self {
try updateAccumulatingResult(&result, element)
}
}
}
Is this a bug in the compiler? Should we revisit adding an overload?