Skip to content

Commit 0ca42e9

Browse files
committed
[string] Shrink storage class sizes.
* Don't allocate breadrumbs pointer if under threshold * Increase breadrumbs threshold * Linear 16-byte bucketing until 128 bytes, malloc_size after * Allow cap less than _SmallString.capacity (bridging non-ASCII) This change decreases the amount of heap usage for moderate-length strings (< 64 UTF-8 code units in length) and increases the amount of spare code unit capacity available (less growth needed). Average improvements for moderate-length strings: * 64-bit: on average, 8 bytes saved and 4 bytes of extra capacity * 32-bit: on average, 4 bytes saved and 6 bytes of extra capacity Additionally, on 32-bit, large-length strings also gain an average of 6 bytes of extra spare capacity. Details: On 64-bit, half of moderate-length allocations will save 16 bytes while the other half get an extra 8 bytes of spare capacity. On 32-bit, a quarter of moderate-length allocations will save 16 bytes, and the rest get an extra 4 bytes of spare capacity. Additionally, 32-bit string's storage class now claims its full allocation, which is its birthright. Prior to this change, we'd have on average 1.5 bytes of spare capacity, and now we have 7.5 bytes of spare capacity. Breadcrumbs threshold is increased from the super-conservative 32 to the pretty-conservative 64. Some speed improvements are incorporated in this change, but more are in flight. Even without those eventual improvements, this is a worthwhile change (ASCII is still fast-pathed and irrelevant to breadcrumbing). For a complex real-world workload, this amounts to around a 5% improvement to transient heap usage due to all strings and a 4% improvement to peak heap usage due to all strings. For moderate-length strings specifically, this gives around 11% improvement to both.
1 parent 1255e10 commit 0ca42e9

9 files changed

+395
-179
lines changed

stdlib/public/core/LegacyABI.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,11 @@ extension String {
7979
range: Range<String.Index>
8080
) { fatalError() }
8181
}
82+
83+
extension String.UTF16View {
84+
// Swift 5.x: This was accidentally shipped as inlinable, but was never used
85+
// from an inlinable context. The definition is kept around for techincal ABI
86+
// compatibility (even though it shouldn't matter), but is unused.
87+
@inlinable @inline(__always)
88+
internal var _shortHeuristic: Int { return 32 }
89+
}

stdlib/public/core/StringBreadcrumbs.swift

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@
1313

1414
// @opaque
1515
internal final class _StringBreadcrumbs {
16-
static var breadcrumbStride: Int { return 32 }
16+
internal static var breadcrumbStride: Int { 64 }
1717

18-
var utf16Length: Int
18+
internal var utf16Length: Int
1919

2020
// TODO: does this need to be a pair?.... Can we be smaller than Int?
21-
var crumbs: [String.Index]
21+
internal var crumbs: [String.Index]
2222

2323
// TODO: Does this need to be inout, unique, or how will we be enforcing
2424
// atomicity?
25-
init(_ str: String) {
25+
internal init(_ str: String) {
2626
let stride = _StringBreadcrumbs.breadcrumbStride
2727

2828
self.crumbs = []
@@ -62,7 +62,7 @@ internal final class _StringBreadcrumbs {
6262
}
6363

6464
extension _StringBreadcrumbs {
65-
var stride: Int {
65+
internal var stride: Int {
6666
@inline(__always) get { return _StringBreadcrumbs.breadcrumbStride }
6767
}
6868

@@ -108,37 +108,3 @@ extension _StringBreadcrumbs {
108108
#endif // INTERNAL_CHECKS_ENABLED
109109
}
110110

111-
extension _StringGuts {
112-
@_effects(releasenone)
113-
internal func getBreadcrumbsPtr() -> UnsafePointer<_StringBreadcrumbs> {
114-
_internalInvariant(hasBreadcrumbs)
115-
116-
let mutPtr: UnsafeMutablePointer<_StringBreadcrumbs?>
117-
if hasNativeStorage {
118-
mutPtr = _object.nativeStorage._breadcrumbsAddress
119-
} else {
120-
mutPtr = UnsafeMutablePointer(
121-
Builtin.addressof(&_object.sharedStorage._breadcrumbs))
122-
}
123-
124-
if _slowPath(mutPtr.pointee == nil) {
125-
populateBreadcrumbs(mutPtr)
126-
}
127-
128-
_internalInvariant(mutPtr.pointee != nil)
129-
// assuming optional class reference and class reference can alias
130-
return UnsafeRawPointer(mutPtr).assumingMemoryBound(to: _StringBreadcrumbs.self)
131-
}
132-
133-
@inline(never) // slow-path
134-
@_effects(releasenone)
135-
internal func populateBreadcrumbs(
136-
_ mutPtr: UnsafeMutablePointer<_StringBreadcrumbs?>
137-
) {
138-
// Thread-safe compare-and-swap
139-
let crumbs = _StringBreadcrumbs(String(self))
140-
_stdlib_atomicInitializeARCRef(
141-
object: UnsafeMutableRawPointer(mutPtr).assumingMemoryBound(to: Optional<AnyObject>.self),
142-
desired: crumbs)
143-
}
144-
}

stdlib/public/core/StringBridge.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ extension String {
462462
// and bridge that instead. Also avoids CF deleting any BOM that may be
463463
// present
464464
var copy = self
465+
// TODO: small capacity minimum is lifted, just need to make native
465466
copy._guts.grow(_SmallString.capacity + 1)
466467
_internalInvariant(!copy._guts.isSmall)
467468
return copy._bridgeToObjectiveCImpl()

stdlib/public/core/StringCreate.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ extension String {
109109
) throws -> Int
110110
) rethrows -> String {
111111
let result = try __StringStorage.create(
112-
uninitializedCapacity: capacity,
112+
uninitializedCodeUnitCapacity: capacity,
113113
initializingUncheckedUTF8With: initializer)
114114

115115
switch validateUTF8(result.codeUnits) {

stdlib/public/core/StringGuts.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,10 @@ extension _StringGuts {
120120

121121
internal var hasSharedStorage: Bool { return _object.hasSharedStorage }
122122

123+
// Whether this string has breadcrumbs
123124
internal var hasBreadcrumbs: Bool {
124-
return hasNativeStorage || hasSharedStorage
125+
return hasSharedStorage
126+
|| (hasNativeStorage && _object.nativeStorage.hasBreadcrumbs)
125127
}
126128
}
127129

stdlib/public/core/StringGutsRangeReplaceable.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,16 @@ extension _StringGuts {
7979
_internalInvariant(
8080
self.uniqueNativeCapacity == nil || self.uniqueNativeCapacity! < n)
8181

82+
// TODO: Dont' do this! Growth should only happen for append...
8283
let growthTarget = Swift.max(n, (self.uniqueNativeCapacity ?? 0) * 2)
8384

8485
if _fastPath(isFastUTF8) {
8586
let isASCII = self.isASCII
8687
let storage = self.withFastUTF8 {
8788
__StringStorage.create(
88-
initializingFrom: $0, capacity: growthTarget, isASCII: isASCII)
89+
initializingFrom: $0,
90+
codeUnitCapacity: growthTarget,
91+
isASCII: isASCII)
8992
}
9093

9194
self = _StringGuts(storage)

0 commit comments

Comments
 (0)