Skip to content

Commit 1699134

Browse files
committed
---
yaml --- r: 316923 b: refs/heads/master-rebranch c: 45af5a4 h: refs/heads/master i: 316921: 3d928ad 316919: e0b1ca6
1 parent 9ed131e commit 1699134

File tree

10 files changed

+172
-120
lines changed

10 files changed

+172
-120
lines changed

[refs]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1457,4 +1457,4 @@ refs/tags/swift-DEVELOPMENT-SNAPSHOT-2019-08-02-a: ddd2b2976aa9bfde5f20fe37f6bd2
14571457
refs/tags/swift-DEVELOPMENT-SNAPSHOT-2019-08-03-a: 171cc166f2abeb5ca2a4003700a8a78a108bd300
14581458
refs/heads/benlangmuir-patch-1: baaebaf39d52f3bf36710d4fe40cf212e996b212
14591459
refs/heads/i-do-redeclare: 8c4e6d5de5c1e3f0a2cedccf319df713ea22c48e
1460-
refs/heads/master-rebranch: a44735acb668755ab09c5628fc18d634dd7a9c2c
1460+
refs/heads/master-rebranch: 45af5a4ec047070fee1bd822a612704f3be7287b

branches/master-rebranch/stdlib/public/core/Dictionary.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -442,8 +442,8 @@ public struct Dictionary<Key: Hashable, Value> {
442442
/// - Parameter minimumCapacity: The minimum number of key-value pairs that
443443
/// the newly created dictionary should be able to store without
444444
/// reallocating its storage buffer.
445-
@inlinable
446-
public init(minimumCapacity: Int) {
445+
public // FIXME(reserveCapacity): Should be inlinable
446+
init(minimumCapacity: Int) {
447447
_variant = .native(_NativeDictionary(capacity: minimumCapacity))
448448
}
449449

@@ -2074,8 +2074,8 @@ extension Dictionary {
20742074
///
20752075
/// - Parameter minimumCapacity: The requested number of key-value pairs to
20762076
/// store.
2077-
@inlinable
2078-
public mutating func reserveCapacity(_ minimumCapacity: Int) {
2077+
public // FIXME(reserveCapacity): Should be inlinable
2078+
mutating func reserveCapacity(_ minimumCapacity: Int) {
20792079
_variant.reserveCapacity(minimumCapacity)
20802080
_sanityCheck(self.capacity >= minimumCapacity)
20812081
}

branches/master-rebranch/stdlib/public/core/DictionaryStorage.swift

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -359,26 +359,31 @@ final internal class _DictionaryStorage<Key: Hashable, Value>
359359
extension _DictionaryStorage {
360360
@usableFromInline
361361
@_effects(releasenone)
362-
internal static func reallocate(
362+
internal static func copy(
363+
original: _RawDictionaryStorage
364+
) -> _DictionaryStorage {
365+
return allocate(
366+
scale: original._scale,
367+
age: original._age,
368+
seed: original._seed)
369+
}
370+
371+
@usableFromInline
372+
@_effects(releasenone)
373+
static internal func resize(
363374
original: _RawDictionaryStorage,
364-
capacity: Int
365-
) -> (storage: _DictionaryStorage, rehash: Bool) {
366-
_sanityCheck(capacity >= original._count)
375+
capacity: Int,
376+
move: Bool
377+
) -> _DictionaryStorage {
367378
let scale = _HashTable.scale(forCapacity: capacity)
368-
let rehash = (scale != original._scale)
369-
let newStorage = _DictionaryStorage<Key, Value>.allocate(
370-
scale: scale,
371-
// Invalidate indices if we're rehashing.
372-
age: rehash ? nil : original._age
373-
)
374-
return (newStorage, rehash)
379+
return allocate(scale: scale, age: nil, seed: nil)
375380
}
376381

377382
@usableFromInline
378383
@_effects(releasenone)
379384
static internal func allocate(capacity: Int) -> _DictionaryStorage {
380385
let scale = _HashTable.scale(forCapacity: capacity)
381-
return allocate(scale: scale, age: nil)
386+
return allocate(scale: scale, age: nil, seed: nil)
382387
}
383388

384389
#if _runtime(_ObjC)
@@ -390,13 +395,14 @@ extension _DictionaryStorage {
390395
) -> _DictionaryStorage {
391396
let scale = _HashTable.scale(forCapacity: capacity)
392397
let age = _HashTable.age(for: cocoa.object)
393-
return allocate(scale: scale, age: age)
398+
return allocate(scale: scale, age: age, seed: nil)
394399
}
395400
#endif
396401

397402
static internal func allocate(
398403
scale: Int8,
399-
age: Int32?
404+
age: Int32?,
405+
seed: Int?
400406
) -> _DictionaryStorage {
401407
// The entry count must be representable by an Int value; hence the scale's
402408
// peculiar upper bound.
@@ -432,19 +438,10 @@ extension _DictionaryStorage {
432438
truncatingIfNeeded: ObjectIdentifier(storage).hashValue)
433439
}
434440

441+
storage._seed = seed ?? _HashTable.hashSeed(for: storage, scale: scale)
435442
storage._rawKeys = UnsafeMutableRawPointer(keysAddr)
436443
storage._rawValues = UnsafeMutableRawPointer(valuesAddr)
437444

438-
// We use a slightly different hash seed whenever we change the size of the
439-
// hash table, so that we avoid certain copy operations becoming quadratic,
440-
// without breaking value semantics. (For background details, see
441-
// https://bugs.swift.org/browse/SR-3268)
442-
443-
// FIXME: Use true per-instance seeding instead. Per-capacity seeding still
444-
// leaves hash values the same in same-sized tables, which may affect
445-
// operations on two tables at once. (E.g., union.)
446-
storage._seed = Int(scale)
447-
448445
// Initialize hash table metadata.
449446
storage._hashTable.clear()
450447
return storage

branches/master-rebranch/stdlib/public/core/DictionaryVariant.swift

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ extension Dictionary._Variant {
109109

110110
/// Reserves enough space for the specified number of elements to be stored
111111
/// without reallocating additional storage.
112-
@inlinable
113112
internal mutating func reserveCapacity(_ capacity: Int) {
114113
switch self {
115114
case .native:
@@ -328,28 +327,22 @@ extension Dictionary._Variant {
328327
}
329328
}
330329

331-
/// Ensure uniquely held native storage, while preserving the given index.
332-
/// (If the variant had bridged storage, then the returned index will be the
333-
/// corresponding native representation. Otherwise it's kept the same.)
334330
@inlinable
335331
@inline(__always)
336332
internal mutating func ensureUniqueNative() -> _NativeDictionary<Key, Value> {
337-
switch self {
338-
case .native:
339-
let isUnique = isUniquelyReferenced()
340-
if !isUnique {
341-
let rehashed = asNative.copy(capacity: asNative.capacity)
342-
_sanityCheck(!rehashed)
343-
}
344-
return asNative
345333
#if _runtime(_ObjC)
346-
case .cocoa(let cocoa):
334+
if case .cocoa(let cocoa) = self {
347335
cocoaPath()
348336
let native = _NativeDictionary<Key, Value>(cocoa)
349337
self = .native(native)
350338
return native
339+
}
351340
#endif
341+
let isUnique = isUniquelyReferenced()
342+
if !isUnique {
343+
asNative.copy()
352344
}
345+
return asNative
353346
}
354347

355348
@inlinable

branches/master-rebranch/stdlib/public/core/HashTable.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,37 @@ extension _HashTable {
8989
let hash = ObjectIdentifier(cocoa).hashValue
9090
return Int32(truncatingIfNeeded: hash)
9191
}
92+
93+
internal static func hashSeed(
94+
for object: AnyObject,
95+
scale: Int8
96+
) -> Int {
97+
#if false // FIXME: Enable per-instance seeding
98+
// We generate a new hash seed whenever a new hash table is allocated and
99+
// whenever an existing table is resized, so that we avoid certain copy
100+
// operations becoming quadratic. (For background details, see
101+
// https://bugs.swift.org/browse/SR-3268)
102+
//
103+
// Note that we do reuse the existing seed when making copy-on-write copies
104+
// so that we avoid breaking value semantics.
105+
if Hasher._isDeterministic {
106+
// When we're using deterministic hashing, the scale value as the seed is
107+
// still allowed, and it covers most cases. (Unfortunately some operations
108+
// that merge two similar-sized hash tables will still be quadratic.)
109+
return Int(scale)
110+
}
111+
// Use the object address as the hash seed. This is cheaper than
112+
// SystemRandomNumberGenerator, while it has the same practical effect.
113+
// Addresses aren't entirely random, but that's not the goal here -- the
114+
// 128-bit execution seed takes care of randomization. We only need to
115+
// guarantee that no two tables with the same seed can coexist at the same
116+
// time (apart from copy-on-write derivatives of the same table).
117+
return unsafeBitCast(object, to: Int.self)
118+
#else
119+
// Use per-capacity seeding for now.
120+
return Int(scale)
121+
#endif
122+
}
92123
}
93124

94125
extension _HashTable {

branches/master-rebranch/stdlib/public/core/NativeDictionary.swift

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,11 @@ extension _NativeDictionary { // ensureUnique
182182
@inlinable
183183
internal mutating func resize(capacity: Int) {
184184
let capacity = Swift.max(capacity, self.capacity)
185-
let result = _NativeDictionary(
186-
_DictionaryStorage<Key, Value>.allocate(capacity: capacity))
185+
let newStorage = _DictionaryStorage<Key, Value>.resize(
186+
original: _storage,
187+
capacity: capacity,
188+
move: true)
189+
let result = _NativeDictionary(newStorage)
187190
if count > 0 {
188191
for bucket in hashTable {
189192
let key = (_keys + bucket.offset).move()
@@ -199,31 +202,40 @@ extension _NativeDictionary { // ensureUnique
199202
}
200203

201204
@inlinable
202-
internal mutating func copy(capacity: Int) -> Bool {
205+
internal mutating func copyAndResize(capacity: Int) {
203206
let capacity = Swift.max(capacity, self.capacity)
204-
let (newStorage, rehash) = _DictionaryStorage<Key, Value>.reallocate(
207+
let newStorage = _DictionaryStorage<Key, Value>.resize(
205208
original: _storage,
206-
capacity: capacity)
209+
capacity: capacity,
210+
move: false)
207211
let result = _NativeDictionary(newStorage)
208212
if count > 0 {
209-
if rehash {
210-
for bucket in hashTable {
211-
result._unsafeInsertNew(
212-
key: self.uncheckedKey(at: bucket),
213-
value: self.uncheckedValue(at: bucket))
214-
}
215-
} else {
216-
result.hashTable.copyContents(of: hashTable)
217-
result._storage._count = self.count
218-
for bucket in hashTable {
219-
let key = uncheckedKey(at: bucket)
220-
let value = uncheckedValue(at: bucket)
221-
result.uncheckedInitialize(at: bucket, toKey: key, value: value)
222-
}
213+
for bucket in hashTable {
214+
result._unsafeInsertNew(
215+
key: self.uncheckedKey(at: bucket),
216+
value: self.uncheckedValue(at: bucket))
217+
}
218+
}
219+
_storage = result._storage
220+
}
221+
222+
@inlinable
223+
internal mutating func copy() {
224+
let newStorage = _DictionaryStorage<Key, Value>.copy(original: _storage)
225+
_sanityCheck(newStorage._scale == _storage._scale)
226+
_sanityCheck(newStorage._age == _storage._age)
227+
_sanityCheck(newStorage._seed == _storage._seed)
228+
let result = _NativeDictionary(newStorage)
229+
if count > 0 {
230+
result.hashTable.copyContents(of: hashTable)
231+
result._storage._count = self.count
232+
for bucket in hashTable {
233+
let key = uncheckedKey(at: bucket)
234+
let value = uncheckedValue(at: bucket)
235+
result.uncheckedInitialize(at: bucket, toKey: key, value: value)
223236
}
224237
}
225238
_storage = result._storage
226-
return rehash
227239
}
228240

229241
/// Ensure storage of self is uniquely held and can hold at least `capacity`
@@ -234,14 +246,18 @@ extension _NativeDictionary { // ensureUnique
234246
if _fastPath(capacity <= self.capacity && isUnique) {
235247
return false
236248
}
237-
guard isUnique else {
238-
return copy(capacity: capacity)
249+
if isUnique {
250+
resize(capacity: capacity)
251+
return true
252+
}
253+
if capacity <= self.capacity {
254+
copy()
255+
return false
239256
}
240-
resize(capacity: capacity)
257+
copyAndResize(capacity: capacity)
241258
return true
242259
}
243260

244-
@inlinable
245261
internal mutating func reserveCapacity(_ capacity: Int, isUnique: Bool) {
246262
_ = ensureUnique(isUnique: isUnique, capacity: capacity)
247263
}
@@ -552,7 +568,8 @@ extension _NativeDictionary { // Deletion
552568
let scale = self._storage._scale
553569
_storage = _DictionaryStorage<Key, Value>.allocate(
554570
scale: scale,
555-
age: nil)
571+
age: nil,
572+
seed: nil)
556573
return
557574
}
558575
for bucket in hashTable {
@@ -570,8 +587,10 @@ extension _NativeDictionary { // High-level operations
570587
internal func mapValues<T>(
571588
_ transform: (Value) throws -> T
572589
) rethrows -> _NativeDictionary<Key, T> {
573-
let result = _NativeDictionary<Key, T>(capacity: capacity)
574-
// Because the keys in the current and new buffer are the same, we can
590+
let resultStorage = _DictionaryStorage<Key, T>.copy(original: _storage)
591+
_sanityCheck(resultStorage._seed == _storage._seed)
592+
let result = _NativeDictionary<Key, T>(resultStorage)
593+
// Because the current and new buffer have the same scale and seed, we can
575594
// initialize to the same locations in the new buffer, skipping hash value
576595
// recalculations.
577596
for bucket in hashTable {

0 commit comments

Comments
 (0)