Skip to content

Commit 8b7ce27

Browse files
authored
Merge pull request swiftlang#29068 from eeckstein/array-improvements
stdlib: code size improvements for Array
2 parents f65005c + 60a0718 commit 8b7ce27

File tree

6 files changed

+229
-83
lines changed

6 files changed

+229
-83
lines changed

stdlib/public/core/Array.swift

Lines changed: 92 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,8 @@ extension Array {
346346
@_semantics("array.make_mutable")
347347
internal mutating func _makeMutableAndUnique() {
348348
if _slowPath(!_buffer.isMutableAndUniquelyReferenced()) {
349-
_buffer = _Buffer(copying: _buffer)
349+
_createNewBuffer(bufferIsUnique: false, minimumCapacity: count,
350+
growForAppend: false)
350351
}
351352
}
352353

@@ -1023,19 +1024,65 @@ extension Array: RangeReplaceableCollection {
10231024
@inlinable
10241025
@_semantics("array.mutate_unknown")
10251026
public mutating func reserveCapacity(_ minimumCapacity: Int) {
1026-
if _buffer.requestUniqueMutableBackingBuffer(
1027-
minimumCapacity: minimumCapacity) == nil {
1027+
_reserveCapacityImpl(minimumCapacity: minimumCapacity,
1028+
growForAppend: false)
1029+
}
1030+
1031+
/// Reserves enough space to store `minimumCapacity` elements.
1032+
/// If a new buffer needs to be allocated and `growForAppend` is true,
1033+
/// the new capacity is calculated using `_growArrayCapacity`, but at least
1034+
/// kept at `minimumCapacity`.
1035+
@_alwaysEmitIntoClient
1036+
internal mutating func _reserveCapacityImpl(
1037+
minimumCapacity: Int, growForAppend: Bool
1038+
) {
1039+
let isUnique = _buffer.isUniquelyReferenced()
1040+
if _slowPath(!isUnique || _getCapacity() < minimumCapacity) {
1041+
_createNewBuffer(bufferIsUnique: isUnique,
1042+
minimumCapacity: Swift.max(minimumCapacity, count),
1043+
growForAppend: growForAppend)
1044+
}
1045+
_internalInvariant(capacity >= minimumCapacity)
1046+
_internalInvariant(capacity == 0 || _buffer.isUniquelyReferenced())
1047+
}
10281048

1029-
let newBuffer = _ContiguousArrayBuffer<Element>(
1030-
_uninitializedCount: count, minimumCapacity: minimumCapacity)
1049+
/// Creates a new buffer, replacing the current buffer.
1050+
///
1051+
/// If `bufferIsUnique` is true, the buffer is assumed to be uniquely
1052+
/// referenced by this array and the elements are moved - instead of copied -
1053+
/// to the new buffer.
1054+
/// The `minimumCapacity` is the lower bound for the new capacity.
1055+
/// If `growForAppend` is true, the new capacity is calculated using
1056+
/// `_growArrayCapacity`, but at least kept at `minimumCapacity`.
1057+
@_alwaysEmitIntoClient
1058+
@inline(never)
1059+
internal mutating func _createNewBuffer(
1060+
bufferIsUnique: Bool, minimumCapacity: Int, growForAppend: Bool
1061+
) {
1062+
let newCapacity = _growArrayCapacity(oldCapacity: _getCapacity(),
1063+
minimumCapacity: minimumCapacity,
1064+
growForAppend: growForAppend)
1065+
let count = _getCount()
1066+
_internalInvariant(newCapacity >= count)
1067+
1068+
let newBuffer = _ContiguousArrayBuffer<Element>(
1069+
_uninitializedCount: count, minimumCapacity: newCapacity)
10311070

1071+
if bufferIsUnique {
1072+
_internalInvariant(_buffer.isUniquelyReferenced())
1073+
1074+
// As an optimization, if the original buffer is unique, we can just move
1075+
// the elements instead of copying.
1076+
let dest = newBuffer.firstElementAddress
1077+
dest.moveInitialize(from: _buffer.firstElementAddress,
1078+
count: count)
1079+
_buffer.count = 0
1080+
} else {
10321081
_buffer._copyContents(
1033-
subRange: _buffer.indices,
1082+
subRange: 0..<count,
10341083
initializing: newBuffer.firstElementAddress)
1035-
_buffer = _Buffer(
1036-
_buffer: newBuffer, shiftedToStartIndex: _buffer.startIndex)
10371084
}
1038-
_internalInvariant(capacity >= minimumCapacity)
1085+
_buffer = _Buffer(_buffer: newBuffer, shiftedToStartIndex: 0)
10391086
}
10401087

10411088
/// Copy the contents of the current buffer to a new unique mutable buffer.
@@ -1054,7 +1101,9 @@ extension Array: RangeReplaceableCollection {
10541101
@_semantics("array.make_mutable")
10551102
internal mutating func _makeUniqueAndReserveCapacityIfNotUnique() {
10561103
if _slowPath(!_buffer.isMutableAndUniquelyReferenced()) {
1057-
_copyToNewBuffer(oldCount: _buffer.count)
1104+
_createNewBuffer(bufferIsUnique: false,
1105+
minimumCapacity: count + 1,
1106+
growForAppend: true)
10581107
}
10591108
}
10601109

@@ -1083,7 +1132,9 @@ extension Array: RangeReplaceableCollection {
10831132
_buffer.isMutableAndUniquelyReferenced())
10841133

10851134
if _slowPath(oldCount + 1 > _buffer.capacity) {
1086-
_copyToNewBuffer(oldCount: oldCount)
1135+
_createNewBuffer(bufferIsUnique: true,
1136+
minimumCapacity: oldCount + 1,
1137+
growForAppend: true)
10871138
}
10881139
}
10891140

@@ -1124,6 +1175,8 @@ extension Array: RangeReplaceableCollection {
11241175
@inlinable
11251176
@_semantics("array.append_element")
11261177
public mutating func append(_ newElement: __owned Element) {
1178+
// Separating uniqueness check and capacity check allows hoisting the
1179+
// uniqueness check out of a loop.
11271180
_makeUniqueAndReserveCapacityIfNotUnique()
11281181
let oldCount = _getCount()
11291182
_reserveCapacityAssumingUniqueBuffer(oldCount: oldCount)
@@ -1160,7 +1213,7 @@ extension Array: RangeReplaceableCollection {
11601213
start: startNewElements,
11611214
count: self.capacity - oldCount)
11621215

1163-
let (remainder,writtenUpTo) = buf.initialize(from: newElements)
1216+
var (remainder,writtenUpTo) = buf.initialize(from: newElements)
11641217

11651218
// trap on underflow from the sequence's underestimate:
11661219
let writtenCount = buf.distance(from: buf.startIndex, to: writtenUpTo)
@@ -1176,30 +1229,39 @@ extension Array: RangeReplaceableCollection {
11761229
if writtenUpTo == buf.endIndex {
11771230
// there may be elements that didn't fit in the existing buffer,
11781231
// append them in slow sequence-only mode
1179-
_buffer._arrayAppendSequence(IteratorSequence(remainder))
1232+
var newCount = _getCount()
1233+
var nextItem = remainder.next()
1234+
while nextItem != nil {
1235+
reserveCapacityForAppend(newElementsCount: 1)
1236+
1237+
let currentCapacity = _getCapacity()
1238+
let base = _buffer.firstElementAddress
1239+
1240+
// fill while there is another item and spare capacity
1241+
while let next = nextItem, newCount < currentCapacity {
1242+
(base + newCount).initialize(to: next)
1243+
newCount += 1
1244+
nextItem = remainder.next()
1245+
}
1246+
_buffer.count = newCount
1247+
}
11801248
}
11811249
}
11821250

11831251
@inlinable
11841252
@_semantics("array.reserve_capacity_for_append")
11851253
internal mutating func reserveCapacityForAppend(newElementsCount: Int) {
1186-
let oldCount = self.count
1187-
let oldCapacity = self.capacity
1188-
let newCount = oldCount + newElementsCount
1189-
11901254
// Ensure uniqueness, mutability, and sufficient storage. Note that
11911255
// for consistency, we need unique self even if newElements is empty.
1192-
self.reserveCapacity(
1193-
newCount > oldCapacity ?
1194-
Swift.max(newCount, _growArrayCapacity(oldCapacity))
1195-
: newCount)
1256+
_reserveCapacityImpl(minimumCapacity: self.count + newElementsCount,
1257+
growForAppend: true)
11961258
}
11971259

11981260
@inlinable
11991261
public mutating func _customRemoveLast() -> Element? {
1262+
_makeMutableAndUnique()
12001263
let newCount = _getCount() - 1
12011264
_precondition(newCount >= 0, "Can't removeLast from an empty Array")
1202-
_makeUniqueAndReserveCapacityIfNotUnique()
12031265
let pointer = (_buffer.firstElementAddress + newCount)
12041266
let element = pointer.move()
12051267
_buffer.count = newCount
@@ -1224,10 +1286,11 @@ extension Array: RangeReplaceableCollection {
12241286
@inlinable
12251287
@discardableResult
12261288
public mutating func remove(at index: Int) -> Element {
1227-
_precondition(index < endIndex, "Index out of range")
1228-
_precondition(index >= startIndex, "Index out of range")
1229-
_makeUniqueAndReserveCapacityIfNotUnique()
1230-
let newCount = _getCount() - 1
1289+
_makeMutableAndUnique()
1290+
let currentCount = _getCount()
1291+
_precondition(index < currentCount, "Index out of range")
1292+
_precondition(index >= 0, "Index out of range")
1293+
let newCount = currentCount - 1
12311294
let pointer = (_buffer.firstElementAddress + index)
12321295
let result = pointer.move()
12331296
pointer.moveInitialize(from: pointer + 1, count: newCount - index)
@@ -1524,9 +1587,8 @@ extension Array {
15241587
public mutating func withUnsafeMutableBufferPointer<R>(
15251588
_ body: (inout UnsafeMutableBufferPointer<Element>) throws -> R
15261589
) rethrows -> R {
1590+
_makeMutableAndUnique()
15271591
let count = self.count
1528-
// Ensure unique storage
1529-
_buffer._outlinedMakeUniqueBuffer(bufferCount: count)
15301592

15311593
// Ensure that body can't invalidate the storage or its bounds by
15321594
// moving self into a temporary working array.
@@ -1638,19 +1700,12 @@ extension Array {
16381700
_precondition(subrange.upperBound <= _buffer.endIndex,
16391701
"Array replace: subrange extends past the end")
16401702

1641-
let oldCount = _buffer.count
16421703
let eraseCount = subrange.count
16431704
let insertCount = newElements.count
16441705
let growth = insertCount - eraseCount
16451706

1646-
if _buffer.requestUniqueMutableBackingBuffer(
1647-
minimumCapacity: oldCount + growth) != nil {
1648-
1649-
_buffer.replaceSubrange(
1650-
subrange, with: insertCount, elementsOf: newElements)
1651-
} else {
1652-
_buffer._arrayOutOfPlaceReplace(subrange, with: newElements, count: insertCount)
1653-
}
1707+
reserveCapacityForAppend(newElementsCount: growth)
1708+
_buffer.replaceSubrange(subrange, with: insertCount, elementsOf: newElements)
16541709
}
16551710
}
16561711

stdlib/public/core/ArrayShared.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,23 @@ internal func _growArrayCapacity(_ capacity: Int) -> Int {
138138
return capacity * 2
139139
}
140140

141+
@_alwaysEmitIntoClient
142+
internal func _growArrayCapacity(
143+
oldCapacity: Int, minimumCapacity: Int, growForAppend: Bool
144+
) -> Int {
145+
if growForAppend {
146+
if oldCapacity < minimumCapacity {
147+
// When appending to an array, grow exponentially.
148+
return Swift.max(minimumCapacity, _growArrayCapacity(oldCapacity))
149+
}
150+
return oldCapacity
151+
}
152+
// If not for append, just use the specified capacity, ignoring oldCapacity.
153+
// This means that we "shrink" the buffer in case minimumCapacity is less
154+
// than oldCapacity.
155+
return minimumCapacity
156+
}
157+
141158
//===--- generic helpers --------------------------------------------------===//
142159

143160
extension _ArrayBufferProtocol {

0 commit comments

Comments
 (0)