Skip to content

Commit 5132c04

Browse files
authored
Merge pull request #16807 from milseman/4_2_smol_reservations
[4.2] Cherry pick: skip allocation if reserve capacity is smol
2 parents 1da926b + aa8b06f commit 5132c04

File tree

4 files changed

+34
-17
lines changed

4 files changed

+34
-17
lines changed

benchmark/single-source/StringBuilder.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ public let StringBuilder = [
2121
name: "StringBuilder",
2222
runFunction: run_StringBuilder,
2323
tags: [.validation, .api, .String]),
24+
BenchmarkInfo(
25+
name: "StringBuilderSmallReservingCapacity",
26+
runFunction: run_StringBuilderSmallReservingCapacity,
27+
tags: [.validation, .api, .String]),
2428
BenchmarkInfo(
2529
name: "StringUTF16Builder",
2630
runFunction: run_StringUTF16Builder,
@@ -48,8 +52,11 @@ public let StringBuilder = [
4852
]
4953

5054
@inline(never)
51-
func buildString(_ i: String) -> String {
55+
func buildString(_ i: String, reservingCapacity: Bool = false) -> String {
5256
var sb = getString(i)
57+
if reservingCapacity {
58+
sb.reserveCapacity(10)
59+
}
5360
for str in ["b","c","d","pizza"] {
5461
sb += str
5562
}
@@ -63,6 +70,13 @@ public func run_StringBuilder(_ N: Int) {
6370
}
6471
}
6572

73+
@inline(never)
74+
public func run_StringBuilderSmallReservingCapacity(_ N: Int) {
75+
for _ in 1...5000*N {
76+
blackHole(buildString("a", reservingCapacity: true))
77+
}
78+
}
79+
6680
@inline(never)
6781
func addString(_ i: String) -> String {
6882
let s = getString(i) + "b" + "c" + "d" + "pizza"
@@ -179,3 +193,4 @@ public func run_StringWordBuilderReservingCapacity(_ N: Int) {
179193
blackHole(buildString(
180194
word: "bumfuzzle", count: 50_000 * N, reservingCapacity: true))
181195
}
196+

stdlib/public/core/StringGuts.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -966,18 +966,22 @@ extension _StringGuts {
966966
}
967967
}
968968

969-
// TODO (TODO: JIRA): check if we're small and still within capacity
969+
// Small strings can accomodate small capacities
970+
if capacity <= _SmallUTF8String.capacity {
971+
return
972+
}
970973

974+
let selfCount = self.count
971975
if isASCII {
972976
let storage = _copyToNativeStorage(
973977
of: UInt8.self,
974-
from: 0..<self.count,
978+
from: 0..<selfCount,
975979
unusedCapacity: Swift.max(capacity - count, 0))
976980
self = _StringGuts(_large: storage)
977981
} else {
978982
let storage = _copyToNativeStorage(
979983
of: UTF16.CodeUnit.self,
980-
from: 0..<self.count,
984+
from: 0..<selfCount,
981985
unusedCapacity: Swift.max(capacity - count, 0))
982986
self = _StringGuts(_large: storage)
983987
}

test/stdlib/NewStringAppending.swift

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,27 +69,25 @@ var s = "⓪" // start non-empty
6969

7070
// To make this test independent of the memory allocator implementation,
7171
// explicitly request initial capacity.
72-
s.reserveCapacity(8)
72+
s.reserveCapacity(16)
7373

74-
// CHECK-NEXT: String(Native(owner: @[[storage0:[x0-9a-f]+]], count: 1, capacity: 8)) = "⓪"
74+
// CHECK-NEXT: String(Native(owner: @[[storage0:[x0-9a-f]+]], count: 1, capacity: 16)) = "⓪"
7575
print("\(repr(s))")
7676

77-
// CHECK-NEXT: String(Native(owner: @[[storage0]], count: 2, capacity: 8)) = "⓪1"
77+
// CHECK-NEXT: String(Native(owner: @[[storage0]], count: 2, capacity: 16)) = "⓪1"
7878
s += "1"
7979
print("\(repr(s))")
8080

81-
// CHECK-NEXT: String(Native(owner: @[[storage0]], count: 8, capacity: 8)) = "⓪1234567"
81+
// CHECK-NEXT: String(Native(owner: @[[storage0]], count: 8, capacity: 16)) = "⓪1234567"
8282
s += "234567"
8383
print("\(repr(s))")
8484

85-
// -- expect a reallocation here
86-
87-
// CHECK-NEXT: String(Native(owner: @[[storage1:[x0-9a-f]+]], count: 9, capacity: 16)) = "⓪12345678"
85+
// CHECK-NEXT: String(Native(owner: @[[storage0:[x0-9a-f]+]], count: 9, capacity: 16)) = "⓪12345678"
8886
// CHECK-NOT: @[[storage0]],
8987
s += "8"
9088
print("\(repr(s))")
9189

92-
// CHECK-NEXT: String(Native(owner: @[[storage1]], count: 16, capacity: 16)) = "⓪123456789012345"
90+
// CHECK-NEXT: String(Native(owner: @[[storage0]], count: 16, capacity: 16)) = "⓪123456789012345"
9391
s += "9012345"
9492
print("\(repr(s))")
9593

validation-test/stdlib/String.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,7 @@ StringTests.test("stringGutsReserve")
948948
base._guts._objectIdentifier != nil &&
949949
isUnique
950950

951-
base.reserveCapacity(0)
951+
base.reserveCapacity(16)
952952
// Now it's unique
953953

954954
// If it was already native and unique, no reallocation
@@ -1065,11 +1065,11 @@ StringTests.test("reserveCapacity") {
10651065
expectNotEqual(id0, s.bufferID)
10661066
s = ""
10671067
print("empty capacity \(s.capacity)")
1068-
s.reserveCapacity(oldCap + 2)
1069-
print("reserving \(oldCap + 2) -> \(s.capacity), width = \(s._guts.byteWidth)")
1068+
s.reserveCapacity(oldCap + 18)
1069+
print("reserving \(oldCap + 18) -> \(s.capacity), width = \(s._guts.byteWidth)")
10701070
let id1 = s.bufferID
1071-
s.insert(contentsOf: repeatElement(x, count: oldCap + 2), at: s.endIndex)
1072-
print("extending by \(oldCap + 2) -> \(s.capacity), width = \(s._guts.byteWidth)")
1071+
s.insert(contentsOf: repeatElement(x, count: oldCap + 18), at: s.endIndex)
1072+
print("extending by \(oldCap + 18) -> \(s.capacity), width = \(s._guts.byteWidth)")
10731073
expectEqual(id1, s.bufferID)
10741074
s.insert(contentsOf: repeatElement(x, count: s.capacity + 100), at: s.endIndex)
10751075
expectNotEqual(id1, s.bufferID)

0 commit comments

Comments
 (0)