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 6 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]);
}

35 changes: 25 additions & 10 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,27 +2133,33 @@ 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>?) {
@objc(getObjects:andKeys:count:)
override func getObjects(
_ objects: UnsafeMutablePointer<AnyObject>?,
andKeys keys: UnsafeMutablePointer<AnyObject>?,
count: Int) {
_precondition(count >= 0, "Invalid count")
// The user is expected to provide a storage of the correct size
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() {
guard i < count else { break }
unmanagedObjects[i] = _bridgeAnythingToObjectiveC(val)
unmanagedKeys[i] = _bridgeAnythingToObjectiveC(key)
}
} else {
// keys nonnull, objects null
for (offset: i, element: (key: key, value: _)) in full.enumerated() {
guard i < count else { break }
unmanagedKeys[i] = _bridgeAnythingToObjectiveC(key)
}
}
} else {
if let unmanagedObjects = _UnmanagedAnyObjectArray(objects) {
// keys null, objects nonnull
for (offset: i, element: (key: _, value: val)) in full.enumerated() {
guard i < count else { break }
unmanagedObjects[i] = _bridgeAnythingToObjectiveC(val)
}
} else {
Expand Down Expand Up @@ -2835,12 +2844,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,8 +2940,10 @@ 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")
bridgeEverything()
// The user is expected to provide a storage of the correct size
var i = 0 // Position in the input storage
Expand All @@ -2942,6 +2954,7 @@ final internal class _SwiftDeferredNSDictionary<Key: Hashable, Value>
// keys nonnull, objects nonnull
for position in 0..<bucketCount {
if bridgedBuffer.isInitializedEntry(at: position) {
guard i < count else { break }
unmanagedObjects[i] = bridgedBuffer.value(at: position)
unmanagedKeys[i] = bridgedBuffer.key(at: position)
i += 1
Expand All @@ -2951,6 +2964,7 @@ final internal class _SwiftDeferredNSDictionary<Key: Hashable, Value>
// keys nonnull, objects null
for position in 0..<bucketCount {
if bridgedBuffer.isInitializedEntry(at: position) {
guard i < count else { break }
unmanagedKeys[i] = bridgedBuffer.key(at: position)
i += 1
}
Expand All @@ -2961,6 +2975,7 @@ final internal class _SwiftDeferredNSDictionary<Key: Hashable, Value>
// keys null, objects nonnull
for position in 0..<bucketCount {
if bridgedBuffer.isInitializedEntry(at: position) {
guard i < count else { break }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could eke out a one less loop iteration by checking this after you increment i -- and adding an early out above for count == 0

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! It can often be more than one iteration, too, since the loop skips over empty buckets.

unmanagedObjects[i] = bridgedBuffer.value(at: position)
i += 1
}
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