diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 0c041688e09a9..25f2daf146b56 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -57,6 +57,7 @@ set(SWIFT_BENCH_MODULES single-source/DictionaryGroup single-source/DictionaryLiteral single-source/DictionaryRemove + single-source/DictionarySubscriptDefault single-source/DictionarySwap single-source/DropFirst single-source/DropLast diff --git a/benchmark/single-source/DictionarySubscriptDefault.swift b/benchmark/single-source/DictionarySubscriptDefault.swift new file mode 100644 index 0000000000000..2c90d012d02eb --- /dev/null +++ b/benchmark/single-source/DictionarySubscriptDefault.swift @@ -0,0 +1,124 @@ +//===--- DictionarySubscriptDefault.swift ---------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import TestsUtils + +public let DictionarySubscriptDefault = [ + BenchmarkInfo(name: "DictionarySubscriptDefaultMutation", + runFunction: run_DictionarySubscriptDefaultMutation, + tags: [.validation, .api, .Dictionary]), + BenchmarkInfo(name: "DictionarySubscriptDefaultMutationArray", + runFunction: run_DictionarySubscriptDefaultMutationArray, + tags: [.validation, .api, .Dictionary]), + BenchmarkInfo(name: "DictionarySubscriptDefaultMutationOfObjects", + runFunction: run_DictionarySubscriptDefaultMutationOfObjects, + tags: [.validation, .api, .Dictionary]), + BenchmarkInfo(name: "DictionarySubscriptDefaultMutationArrayOfObjects", + runFunction: + run_DictionarySubscriptDefaultMutationArrayOfObjects, + tags: [.validation, .api, .Dictionary]), +] + +let count = 10_000 +let result = count / 100 + +@inline(never) +public func run_DictionarySubscriptDefaultMutation(_ N: Int) { + for _ in 1...N { + + var dict = [Int: Int]() + + for i in 0.. Void) { + mutations(&value) + } +} + +class Box : Hashable, P { + var value: T + + init(_ v: T) { + value = v + } + + var hashValue: Int { + return value.hashValue + } + + static func ==(lhs: Box, rhs: Box) -> Bool { + return lhs.value == rhs.value + } +} + +@inline(never) +public func run_DictionarySubscriptDefaultMutationOfObjects(_ N: Int) { + for _ in 1...N { + + var dict = [Box: Box]() + + for i in 0..: [Box]]() + + for i in 0.. : get { return _variantBuffer.maybeGet(key) ?? defaultValue() } - set(newValue) { - // FIXME(performance): this loads and discards the old value. - _variantBuffer.updateValue(newValue, forKey: key) + mutableAddressWithNativeOwner { + let (_, address) = _variantBuffer + .pointerToValue(forKey: key, insertingDefault: defaultValue) + return (address, Builtin.castToNativeObject( + _variantBuffer.asNative._storage)) } } @@ -4867,6 +4869,17 @@ internal enum _Variant${Self}Buffer<${TypeParametersDecl}> : _HashBuffer { } } + @_inlineable // FIXME(sil-serialize-all) + @_versioned // FIXME(sil-serialize-all) + internal mutating func ensureNativeBuffer() { +#if _runtime(_ObjC) + if _fastPath(guaranteedNative) { return } + if case .cocoa(let cocoaBuffer) = self { + migrateDataToNativeBuffer(cocoaBuffer) + } +#endif + } + #if _runtime(_ObjC) @_inlineable // FIXME(sil-serialize-all) @_versioned // FIXME(sil-serialize-all) @@ -5316,6 +5329,42 @@ internal enum _Variant${Self}Buffer<${TypeParametersDecl}> : _HashBuffer { #endif } } + + @_inlineable // FIXME(sil-serialize-all) + @_versioned // FIXME(sil-serialize-all) + internal mutating func nativePointerToValue( + forKey key: Key, insertingDefault defaultValue: () -> Value + ) -> (inserted: Bool, pointer: UnsafeMutablePointer) { + + var (i, found) = asNative._find(key, startBucket: asNative._bucket(key)) + if found { + let pointer = nativePointerToValue(at: ._native(i)) + return (inserted: false, pointer: pointer) + } + + let minCapacity = NativeBuffer.minimumCapacity( + minimumCount: asNative.count + 1, + maxLoadFactorInverse: _hashContainerDefaultMaxLoadFactorInverse) + + let (_, capacityChanged) = ensureUniqueNativeBuffer(minCapacity) + + if capacityChanged { + i = asNative._find(key, startBucket: asNative._bucket(key)).pos + } + + asNative.initializeKey(key, value: defaultValue(), at: i.offset) + asNative.count += 1 + return (inserted: true, pointer: asNative.values + i.offset) + } + + @_inlineable // FIXME(sil-serialize-all) + @_versioned // FIXME(sil-serialize-all) + internal mutating func pointerToValue( + forKey key: Key, insertingDefault defaultValue: () -> Value + ) -> (inserted: Bool, pointer: UnsafeMutablePointer) { + ensureNativeBuffer() + return nativePointerToValue(forKey: key, insertingDefault: defaultValue) + } %end @_inlineable // FIXME(sil-serialize-all) @@ -5360,20 +5409,8 @@ internal enum _Variant${Self}Buffer<${TypeParametersDecl}> : _HashBuffer { internal mutating func insert( _ value: Value, forKey key: Key ) -> (inserted: Bool, memberAfterInsert: Value) { - - if _fastPath(guaranteedNative) { - return nativeInsert(value, forKey: key) - } - - switch self { - case .native: - return nativeInsert(value, forKey: key) -#if _runtime(_ObjC) - case .cocoa(let cocoaBuffer): - migrateDataToNativeBuffer(cocoaBuffer) - return nativeInsert(value, forKey: key) -#endif - } + ensureNativeBuffer() + return nativeInsert(value, forKey: key) } %if Self == 'Dictionary': @@ -5477,20 +5514,8 @@ internal enum _Variant${Self}Buffer<${TypeParametersDecl}> : _HashBuffer { _ keysAndValues: S, uniquingKeysWith combine: (Value, Value) throws -> Value ) rethrows where S.Element == (Key, Value) { - if _fastPath(guaranteedNative) { - try nativeMerge(keysAndValues, uniquingKeysWith: combine) - return - } - - switch self { - case .native: - try nativeMerge(keysAndValues, uniquingKeysWith: combine) -#if _runtime(_ObjC) - case .cocoa(let cocoaStorage): - migrateDataToNativeBuffer(cocoaStorage) - try nativeMerge(keysAndValues, uniquingKeysWith: combine) -#endif - } + ensureNativeBuffer() + try nativeMerge(keysAndValues, uniquingKeysWith: combine) } @_inlineable // FIXME(sil-serialize-all) diff --git a/test/stdlib/Inputs/DictionaryKeyValueTypes.swift b/test/stdlib/Inputs/DictionaryKeyValueTypes.swift index e951b827c2654..9fd935afa7d50 100644 --- a/test/stdlib/Inputs/DictionaryKeyValueTypes.swift +++ b/test/stdlib/Inputs/DictionaryKeyValueTypes.swift @@ -25,6 +25,31 @@ func equalsUnordered(_ lhs: [T], _ rhs: [T]) -> Bool { return lhs.sorted().elementsEqual(rhs.sorted()) } +// A COW wrapper type that holds an Int. +struct TestValueCOWTy { + + class Base { + var value: Int + init(_ value: Int) { self.value = value } + } + + private var base: Base + init(_ value: Int = 0) { self.base = Base(value) } + + var value: Int { + get { return base.value } + set { + if !isKnownUniquelyReferenced(&base) { + base = Base(newValue) + } else { + base.value = newValue + } + } + } + + var baseAddress: Int { return unsafeBitCast(base, to: Int.self) } +} + var _keyCount = _stdlib_AtomicInt(0) var _keySerial = _stdlib_AtomicInt(0) diff --git a/validation-test/stdlib/Dictionary.swift b/validation-test/stdlib/Dictionary.swift index b27f5d5b1eb0f..66e965b0a58bc 100644 --- a/validation-test/stdlib/Dictionary.swift +++ b/validation-test/stdlib/Dictionary.swift @@ -120,6 +120,14 @@ func getCOWFastDictionary() -> Dictionary { return d } +func getCOWFastDictionaryWithCOWValues() -> Dictionary { + var d = Dictionary(minimumCapacity: 10) + d[10] = TestValueCOWTy(1010) + d[20] = TestValueCOWTy(1020) + d[30] = TestValueCOWTy(1030) + return d +} + func getCOWSlowDictionary() -> Dictionary { var d = Dictionary(minimumCapacity: 10) d[TestKeyTy(10)] = TestValueTy(1010) @@ -797,6 +805,32 @@ DictionaryTestSuite.test("COW.Fast.DefaultedSubscriptDoesNotReallocate") { } } +DictionaryTestSuite.test("COW.Fast.DefaultedSubscriptDoesNotCopyValue") { + do { + var d = getCOWFastDictionaryWithCOWValues() + let identityValue30 = d[30]!.baseAddress + + // Increment the value without having to reallocate the underlying Base + // instance, as uniquely referenced. + d[30, default: TestValueCOWTy()].value += 1 + assert(identityValue30 == d[30]!.baseAddress) + assert(d[30]!.value == 1031) + + let value40 = TestValueCOWTy() + let identityValue40 = value40.baseAddress + + // Increment the value, reallocating the underlying Base, as not uniquely + // referenced. + d[40, default: value40].value += 1 + assert(identityValue40 != d[40]!.baseAddress) + assert(d[40]!.value == 1) + + // Keep variables alive. + _fixLifetime(d) + _fixLifetime(value40) + } +} + DictionaryTestSuite.test("COW.Fast.IndexForKeyDoesNotReallocate") { var d = getCOWFastDictionary() var identity1 = d._rawIdentifier()