Skip to content

[stdlib] Speed up bridged Dictionary instances #17742

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ set(SWIFT_BENCH_MODULES
single-source/DictTest4
single-source/DictTest4Legacy
single-source/DictionaryBridge
single-source/DictionaryBridgeToObjC
single-source/DictionaryCopy
single-source/DictionaryGroup
single-source/DictionaryKeysContains
Expand Down
86 changes: 86 additions & 0 deletions benchmark/single-source/DictionaryBridgeToObjC.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//===--- DictionaryBridgeToObjC.swift -------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 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
//
//===----------------------------------------------------------------------===//

// Performance benchmark for common operations on Dictionary values bridged to
// NSDictionary.
import TestsUtils
#if _runtime(_ObjC)
import Foundation

public let DictionaryBridgeToObjC = [
BenchmarkInfo(
name: "DictionaryBridgeToObjC_Bridge",
runFunction: run_DictionaryBridgeToObjC_BridgeToObjC,
tags: [.validation, .api, .Dictionary, .bridging]),
BenchmarkInfo(
name: "DictionaryBridgeToObjC_Access",
runFunction: run_DictionaryBridgeToObjC_Access,
tags: [.validation, .api, .Dictionary, .bridging]),
BenchmarkInfo(
name: "DictionaryBridgeToObjC_BulkAccess",
runFunction: run_DictionaryBridgeToObjC_BulkAccess,
tags: [.validation, .api, .Dictionary, .bridging])
]

let numbers: [String: Int] = [
"one": 1,
"two": 2,
"three": 3,
"four": 4,
"five": 5,
"six": 6,
"seven": 7,
"eight": 8,
"nine": 9,
"ten": 10,
"eleven": 11,
"twelve": 12,
"thirteen": 13,
"fourteen": 14,
"fifteen": 15,
"sixteen": 16,
"seventeen": 17,
"eighteen": 18,
"nineteen": 19,
"twenty": 20
]

@inline(never)
public func run_DictionaryBridgeToObjC_BridgeToObjC(_ N: Int) {
for _ in 1 ... 100 * N {
blackHole(numbers as NSDictionary)
}
}

@inline(never)
public func run_DictionaryBridgeToObjC_Access(_ N: Int) {
let d = numbers as NSDictionary
blackHole(d.object(forKey: "one")) // Force bridging of contents
for _ in 1 ... 100 * N {
for key in numbers.keys {
CheckResults(identity(d).object(forKey: key) != nil)
}
}
}

@inline(never)
public func run_DictionaryBridgeToObjC_BulkAccess(_ N: Int) {
let d = numbers as NSDictionary
for _ in 1 ... 100 * N {
let d2 = NSDictionary(dictionary: identity(d))
CheckResults(d2.count == d.count)
}
}

#else // !_runtime(ObjC)
public let DictionaryBridgeToObjC: [BenchmarkInfo] = []
#endif
2 changes: 2 additions & 0 deletions benchmark/utils/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import DictTest3
import DictTest4
import DictTest4Legacy
import DictionaryBridge
import DictionaryBridgeToObjC
import DictionaryCopy
import DictionaryGroup
import DictionaryKeysContains
Expand Down Expand Up @@ -215,6 +216,7 @@ registerBenchmark(Dictionary3)
registerBenchmark(Dictionary4)
registerBenchmark(Dictionary4Legacy)
registerBenchmark(DictionaryBridge)
registerBenchmark(DictionaryBridgeToObjC)
registerBenchmark(DictionaryCopy)
registerBenchmark(DictionaryGroup)
registerBenchmark(DictionaryKeysContains)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public func withOverriddenLocaleCurrentLocale<Result>(
public func autoreleasepoolIfUnoptimizedReturnAutoreleased(
invoking body: () -> Void
) {
#if arch(i386) && (os(iOS) || os(watchOS))
#if targetEnvironment(simulator) && arch(i386) && (os(iOS) || os(watchOS))
autoreleasepool(invoking: body)
#else
body()
Expand Down Expand Up @@ -100,23 +100,26 @@ extension NSArray {
}
}

@_silgen_name("NSDictionary_getObjects")
func NSDictionary_getObjects(
@_silgen_name("NSDictionary_getObjectsAndKeysWithCount")
func NSDictionary_getObjectsAndKeysWithCount(
nsDictionary: NSDictionary,
objects: AutoreleasingUnsafeMutablePointer<AnyObject?>?,
andKeys keys: AutoreleasingUnsafeMutablePointer<AnyObject?>?
andKeys keys: AutoreleasingUnsafeMutablePointer<AnyObject?>?,
count: Int
)

extension NSDictionary {
@nonobjc // FIXME: there should be no need in this attribute.
public func available_getObjects(
_ objects: AutoreleasingUnsafeMutablePointer<AnyObject?>?,
andKeys keys: AutoreleasingUnsafeMutablePointer<AnyObject?>?
andKeys keys: AutoreleasingUnsafeMutablePointer<AnyObject?>?,
count: Int
) {
return NSDictionary_getObjects(
return NSDictionary_getObjectsAndKeysWithCount(
nsDictionary: self,
objects: objects,
andKeys: keys)
andKeys: keys,
count: count)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@

SWIFT_CC(swift) SWIFT_RUNTIME_LIBRARY_VISIBILITY
extern "C" void
NSDictionary_getObjects(NSDictionary *_Nonnull nsDictionary,
id *objects, id *keys) {
[nsDictionary getObjects:objects andKeys:keys];
NSDictionary_getObjectsAndKeysWithCount(NSDictionary *_Nonnull nsDictionary,
id *objects, id *keys,
NSInteger count) {
[nsDictionary getObjects:objects andKeys:keys count:count];
SWIFT_CC_PLUSONE_GUARD([nsDictionary release]);
}

55 changes: 37 additions & 18 deletions stdlib/public/core/Dictionary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1582,7 +1582,7 @@ internal func _stdlib_NSDictionary_allKeys(_ nsd: _NSDictionary)
let storage = _HeapBuffer<Int, AnyObject>(
_HeapBufferStorage<Int, AnyObject>.self, count, count)

nsd.getObjects(nil, andKeys: storage.baseAddress)
nsd.getObjects(nil, andKeys: storage.baseAddress, count: count)
return storage
}
#endif
Expand Down Expand Up @@ -1959,8 +1959,11 @@ internal class _RawNativeDictionaryStorage
}

@inlinable // FIXME(sil-serialize-all)
internal func getObjects(_ objects: UnsafeMutablePointer<AnyObject>?,
andKeys keys: UnsafeMutablePointer<AnyObject>?) {
@objc(getObjects:andKeys:count:)
internal func getObjects(
_ objects: UnsafeMutablePointer<AnyObject>?,
andKeys keys: UnsafeMutablePointer<AnyObject>?,
count: Int) {
// Do nothing, we're empty
}
#endif
Expand Down Expand Up @@ -2130,28 +2133,38 @@ final internal class _HashableTypedNativeDictionaryStorage<Key: Hashable, Value>

// We also override the following methods for efficiency.
@inlinable // FIXME(sil-serialize-all)
@objc
override func getObjects(_ objects: UnsafeMutablePointer<AnyObject>?,
andKeys keys: UnsafeMutablePointer<AnyObject>?) {
// The user is expected to provide a storage of the correct size
@objc(getObjects:andKeys:count:)
override func getObjects(
_ objects: UnsafeMutablePointer<AnyObject>?,
andKeys keys: UnsafeMutablePointer<AnyObject>?,
count: Int) {
_precondition(count >= 0, "Invalid count")
guard count > 0 else { return }
var i = 0 // Current position in the output buffers
if let unmanagedKeys = _UnmanagedAnyObjectArray(keys) {
if let unmanagedObjects = _UnmanagedAnyObjectArray(objects) {
// keys nonnull, objects nonnull
for (offset: i, element: (key: key, value: val)) in full.enumerated() {
unmanagedObjects[i] = _bridgeAnythingToObjectiveC(val)
for (key, value) in full {
unmanagedObjects[i] = _bridgeAnythingToObjectiveC(value)
unmanagedKeys[i] = _bridgeAnythingToObjectiveC(key)
i += 1
guard i < count else { break }
}
} else {
// keys nonnull, objects null
for (offset: i, element: (key: key, value: _)) in full.enumerated() {
for (key, _) in full {
unmanagedKeys[i] = _bridgeAnythingToObjectiveC(key)
i += 1
guard i < count else { break }
}
}
} else {
if let unmanagedObjects = _UnmanagedAnyObjectArray(objects) {
// keys null, objects nonnull
for (offset: i, element: (key: _, value: val)) in full.enumerated() {
unmanagedObjects[i] = _bridgeAnythingToObjectiveC(val)
for (_, value) in full {
unmanagedObjects[i] = _bridgeAnythingToObjectiveC(value)
i += 1
guard i < count else { break }
}
} else {
// do nothing, both are null
Expand Down Expand Up @@ -2835,12 +2848,13 @@ final internal class _SwiftDeferredNSDictionary<Key: Hashable, Value>
}

@inlinable // FIXME(sil-serialize-all)
@objc
@objc(getObjects:andKeys:count:)
internal func getObjects(
_ objects: UnsafeMutablePointer<AnyObject>?,
andKeys keys: UnsafeMutablePointer<AnyObject>?
andKeys keys: UnsafeMutablePointer<AnyObject>?,
count: Int
) {
bridgedAllKeysAndValues(objects, keys)
bridgedAllKeysAndValues(objects, keys, count)
}

@inlinable // FIXME(sil-serialize-all)
Expand Down Expand Up @@ -2930,11 +2944,13 @@ final internal class _SwiftDeferredNSDictionary<Key: Hashable, Value>
@nonobjc
internal func bridgedAllKeysAndValues(
_ objects: UnsafeMutablePointer<AnyObject>?,
_ keys: UnsafeMutablePointer<AnyObject>?
_ keys: UnsafeMutablePointer<AnyObject>?,
_ count: Int
) {
_precondition(count >= 0, "Invalid count")
guard count > 0 else { return }
bridgeEverything()
// The user is expected to provide a storage of the correct size
var i = 0 // Position in the input storage
var i = 0 // Current position in the output buffers
let bucketCount = nativeBuffer.bucketCount

if let unmanagedKeys = _UnmanagedAnyObjectArray(keys) {
Expand All @@ -2945,6 +2961,7 @@ final internal class _SwiftDeferredNSDictionary<Key: Hashable, Value>
unmanagedObjects[i] = bridgedBuffer.value(at: position)
unmanagedKeys[i] = bridgedBuffer.key(at: position)
i += 1
guard i < count else { break }
}
}
} else {
Expand All @@ -2953,6 +2970,7 @@ final internal class _SwiftDeferredNSDictionary<Key: Hashable, Value>
if bridgedBuffer.isInitializedEntry(at: position) {
unmanagedKeys[i] = bridgedBuffer.key(at: position)
i += 1
guard i < count else { break }
}
}
}
Expand All @@ -2963,6 +2981,7 @@ final internal class _SwiftDeferredNSDictionary<Key: Hashable, Value>
if bridgedBuffer.isInitializedEntry(at: position) {
unmanagedObjects[i] = bridgedBuffer.value(at: position)
i += 1
guard i < count else { break }
}
}
} else {
Expand Down
16 changes: 11 additions & 5 deletions stdlib/public/core/ShadowProtocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,12 @@ public protocol _NSDictionaryCore :
@objc(copyWithZone:)
func copy(with zone: _SwiftNSZone?) -> AnyObject

func getObjects(_ objects: UnsafeMutablePointer<AnyObject>?,
andKeys keys: UnsafeMutablePointer<AnyObject>?)
@objc(getObjects:andKeys:count:)
func getObjects(
_ objects: UnsafeMutablePointer<AnyObject>?,
andKeys keys: UnsafeMutablePointer<AnyObject>?,
count: Int
)

@objc(countByEnumeratingWithState:objects:count:)
func countByEnumerating(
Expand All @@ -125,9 +129,11 @@ public protocol _NSDictionaryCore :
public protocol _NSDictionary : _NSDictionaryCore {
// Note! This API's type is different from what is imported by the clang
// importer.
func getObjects(_ objects: UnsafeMutablePointer<AnyObject>?,
andKeys keys: UnsafeMutablePointer<AnyObject>?)
}
func getObjects(
_ objects: UnsafeMutablePointer<AnyObject>?,
andKeys keys: UnsafeMutablePointer<AnyObject>?,
count: Int)
}

/// A shadow for the "core operations" of NSSet.
///
Expand Down
Loading