Skip to content

Adopt bulk Dictionary creation in bridging #21235

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 6 commits into from
Dec 21, 2018

Conversation

Catfish-Man
Copy link
Contributor

@Catfish-Man Catfish-Man commented Dec 12, 2018

Karoy's new Dictionary initializer gives us access to the uninitialized backing buffers, which we can bulk-copy the contents of the NSDictionary into.

@Catfish-Man Catfish-Man requested a review from lorentey December 12, 2018 01:48
@Catfish-Man Catfish-Man self-assigned this Dec 12, 2018
@Catfish-Man
Copy link
Contributor Author

I think I understand what's going wrong now :)

@Catfish-Man
Copy link
Contributor Author

Ok maybe I don't. Somehow we're blowing up trying to retain the thing we just copied out of the NSDictionary. Before I thought we just weren't retaining it (which is probably true, but wasn't the problem).

@@ -107,19 +107,84 @@ extension Dictionary : _ObjectiveCBridgeable {
result = builder.take()
}

private static func _typePun<T>(index:Int, of buffer: UnsafeBufferPointer<AnyObject>, to: T.Type) -> UnsafeMutablePointer<T> {
let typedBase = UnsafeMutableRawPointer(mutating: buffer.baseAddress!).assumingMemoryBound(to: T.self)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This part is a bit ridiculous, but I'm trying really hard to a) avoid binding the memory at base to T prematurely, and b) avoid doing pointer math myself when Swift can already learn the size and alignment from T itself

_ objectBuffer: UnsafeBufferPointer<AnyObject>,
to: T.Type
) {
for i in (0..<objectBuffer.count).reversed() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has to go in reverse because if sizeof(T) > sizeof(AnyObject), we'll be shifting each element as we bridge it

@Catfish-Man
Copy link
Contributor Author

@swift-ci please benchmark

@Catfish-Man
Copy link
Contributor Author

@swift-ci please test

@Catfish-Man Catfish-Man changed the title [DNM] Adopt bulk Dictionary creation in bridging Adopt bulk Dictionary creation in bridging Dec 12, 2018
@Catfish-Man
Copy link
Contributor Author

@swift-ci please benchmark

@Catfish-Man
Copy link
Contributor Author

@swift-ci please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - cd328ef75156fe944411c294232b5c7c9be6b082

@Catfish-Man Catfish-Man force-pushed the cheaper-by-the-dozen branch 2 times, most recently from d55e8f1 to 8783e66 Compare December 13, 2018 00:33
@Catfish-Man
Copy link
Contributor Author

@swift-ci please test

@Catfish-Man
Copy link
Contributor Author

@swift-ci please benchmark

@Catfish-Man
Copy link
Contributor Author

@swift-ci please test

@Catfish-Man
Copy link
Contributor Author

@swift-ci please benchmark

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - 2853a450b788edab2fbae223632244e064db15af

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - cd328ef75156fe944411c294232b5c7c9be6b082

@swift-ci
Copy link
Contributor

Build comment file:

Performance: -O

TEST OLD NEW DELTA RATIO
Regression
DataSetCountSmall 137 154 +12.4% 0.89x
Improvement
ObjectiveCBridgeFromNSDictionaryAnyObject 65922 27481 -58.3% 2.40x
NSDictionaryCastToSwift 8351 5898 -29.4% 1.42x
DictionaryBridge 1144 841 -26.5% 1.36x
Breadcrumbs.CopyUTF16CodeUnits.Mixed 64 56 -12.5% 1.14x
JSONPerfDecode 499 442 -11.4% 1.13x
PlistPerfDecode 590 532 -9.8% 1.11x
Breadcrumbs.UTF16ToIdx.longMixed 386 357 -7.5% 1.08x

Performance: -Osize

TEST OLD NEW DELTA RATIO
Regression
DataSetCountSmall 140 157 +12.1% 0.89x
Improvement
ObjectiveCBridgeFromNSDictionaryAnyObject 72618 28178 -61.2% 2.58x
DictionaryBridge 1301 865 -33.5% 1.50x
NSDictionaryCastToSwift 7728 5415 -29.9% 1.43x
Breadcrumbs.CopyUTF16CodeUnits.Mixed 66 56 -15.2% 1.18x (?)
PlistPerfDecode 583 525 -9.9% 1.11x
JSONPerfDecode 507 457 -9.9% 1.11x

Performance: -Onone

TEST OLD NEW DELTA RATIO
Regression
EqualSubstringSubstring 46 50 +8.7% 0.92x
DataSetCountSmall 200 217 +8.5% 0.92x (?)
Improvement
ObjectiveCBridgeFromNSDictionaryAnyObject 70697 30549 -56.8% 2.31x
DictionaryBridge 1296 898 -30.7% 1.44x
ArrayOfGenericPOD2 1180 1067 -9.6% 1.11x (?)
ArrayOfPOD 859 780 -9.2% 1.10x
JSONPerfDecode 509 468 -8.1% 1.09x

Code size: -swiftlibs

TEST OLD NEW DELTA RATIO
Improvement
libswiftFoundation.dylib 1654784 1630208 -1.5% 1.02x
How to read the data The tables contain differences in performance which are larger than 8% and differences in code size which are larger than 1%.

If you see any unexpected regressions, you should consider fixing the
regressions before you merge the PR.

Noise: Sometimes the performance results (not code size!) contain false
alarms. Unexpected regressions which are marked with '(?)' are probably noise.
If you see regressions which you cannot explain you can try to run the
benchmarks again. If regressions still show up, please consult with the
performance team (@eeckstein).

Hardware Overview
  Model Name: Mac Pro
  Model Identifier: MacPro6,1
  Processor Name: 12-Core Intel Xeon E5
  Processor Speed: 2.7 GHz
  Number of Processors: 1
  Total Number of Cores: 12
  L2 Cache (per Core): 256 KB
  L3 Cache: 30 MB
  Memory: 64 GB
--------------

@Catfish-Man Catfish-Man requested a review from atrick December 13, 2018 01:32
@Catfish-Man
Copy link
Contributor Author

@atrick please feel free to ignore the dictionary machinery, it was suggested that I run the type punning/unsafe pointer games I'm playing past you to make sure they're ok

@Catfish-Man
Copy link
Contributor Author

Hm. Duplicate keys test wasn't failing locally… I'll have to have a look later.

) -> Bool {
if _isBridgedVerbatimToObjectiveC(T.self) {
_verbatimBridge(objectBuffer, count: count, to: T.self)
return true
Copy link
Member

@lorentey lorentey Dec 13, 2018

Choose a reason for hiding this comment

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

This doesn't look right -- verbatim bridging should still perform a checked downcast, or we would allow casting an NSDictionary of NSString keys to [NSNumber: Any].

(Maybe there is room to add an unchecked downcasting variant for use in case we're totally sure.)

Copy link
Member

Choose a reason for hiding this comment

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

Oops, it looks like we don't perform this check for verbatim-bridged forced downcasts on master, either -- we only check conditional downcasts. That seems like a bug!

Copy link
Member

@lorentey lorentey Dec 13, 2018

Choose a reason for hiding this comment

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

Except it isn't -- the check is of course still performed when we're downcasting individual items. This is by design; bridging is done lazily in these cases. Sorry for the brain hiccup!

Despite my confusion about forced casts, conditional casts are always expected to eagerly check that all items can be bridged/downcasted to the target type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I have this right in the most recent update I pushed. The verbatim ones use ? and !, and the non verbatim ones use Swift._forceBridgeFromObjectiveC and Swift._conditionallyBridgeFromObjectiveC. It's possible we could merge some of the cases now, I'm not entirely sure.

Copy link
Member

@lorentey lorentey left a comment

Choose a reason for hiding this comment

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

I noticed two issues: (1) downcasts can and should fail even with verbatim bridging, and (2) if an optional cast fails, we should make sure we don't leak previously bridged items. (There should be regressions tests for these -- I'll add some soon.)

The type punning looks valid to me, but I'm by no means an expert in that! Nice work

Update: It is okay not to check nsdict as! [K: V] at the point of the cast in the verbatim-bridging case -- however, nsdict as? [K: V] must still be fully verified.

success = _conditionallyBridge(anyKeyPtr, count: numElems, to: Key.self) &&
_conditionallyBridge(anyValuePtr, count: numElems, to: Value.self)

initializedCount = success ? numElems : 0
Copy link
Member

Choose a reason for hiding this comment

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

Ah, this is a neat trick. 👌

@Catfish-Man
Copy link
Contributor Author

Figured out why I wasn't seeing this locally. I needed to rebase.

@Catfish-Man
Copy link
Contributor Author

The memory leaks the new tests noticed are really looking like a compiler bug

@Catfish-Man
Copy link
Contributor Author

@swift-ci please test and merge

@Catfish-Man
Copy link
Contributor Author

@swift-ci please benchmark

success = _conditionallyBridge(objectVals,
count: numElems, to: Value.self)
if !success {
(UnsafeMutableRawPointer(mutating: objectKeys).assumingMemoryBound(to:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For reference, this was the fix for the memory leak >.< cleaning up the buffer that failed worked fine, but if the second buffer failed we wouldn't clean up the first one.

@Catfish-Man
Copy link
Contributor Author

@swift-ci please benchmark

@Catfish-Man
Copy link
Contributor Author

@swift-ci please test and merge

@swift-ci
Copy link
Contributor

Build comment file:

Performance: -O

TEST OLD NEW DELTA RATIO
Improvement
ObjectiveCBridgeFromNSDictionaryAnyObject 65799 40500 -38.4% 1.62x
NSDictionaryCastToSwift 6860 5062 -26.2% 1.36x
DictionaryBridge 1091 839 -23.1% 1.30x
JSONPerfDecode 452 369 -18.4% 1.22x
PlistPerfDecode 521 450 -13.6% 1.16x

Performance: -Osize

TEST OLD NEW DELTA RATIO
Improvement
ObjectiveCBridgeFromNSDictionaryAnyObject 69098 42475 -38.5% 1.63x
NSDictionaryCastToSwift 7753 5129 -33.8% 1.51x
DictionaryBridge 1157 806 -30.3% 1.44x
JSONPerfDecode 469 364 -22.4% 1.29x
PlistPerfDecode 536 451 -15.9% 1.19x

Performance: -Onone

TEST OLD NEW DELTA RATIO
Improvement
ObjectiveCBridgeFromNSDictionaryAnyObject 62732 45210 -27.9% 1.39x
DictionaryBridge 1131 879 -22.3% 1.29x
JSONPerfDecode 460 383 -16.7% 1.20x
PlistPerfDecode 537 461 -14.2% 1.16x

Code size: -swiftlibs

TEST OLD NEW DELTA RATIO
Improvement
libswiftFoundation.dylib 1617920 1597440 -1.3% 1.01x
How to read the data The tables contain differences in performance which are larger than 8% and differences in code size which are larger than 1%.

If you see any unexpected regressions, you should consider fixing the
regressions before you merge the PR.

Noise: Sometimes the performance results (not code size!) contain false
alarms. Unexpected regressions which are marked with '(?)' are probably noise.
If you see regressions which you cannot explain you can try to run the
benchmarks again. If regressions still show up, please consult with the
performance team (@eeckstein).

Hardware Overview
  Model Name: Mac Pro
  Model Identifier: MacPro6,1
  Processor Name: 8-Core Intel Xeon E5
  Processor Speed: 3 GHz
  Number of Processors: 1
  Total Number of Cores: 8
  L2 Cache (per Core): 256 KB
  L3 Cache: 25 MB
  Memory: 16 GB
--------------

@Catfish-Man
Copy link
Contributor Author

@swift-ci please test and merge

@Catfish-Man
Copy link
Contributor Author

Now what >.<

@jrose-apple
Copy link
Contributor

That looks like the spurious failure in the Ackermann benchmark @palimondo is fixing. Not your fault.

@swift-ci Please test macOS

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - 01826d9b29b14e65eea28cfea0c3fab2bccc97ca

@Catfish-Man Catfish-Man merged commit 4e4325d into swiftlang:master Dec 21, 2018
@Catfish-Man Catfish-Man deleted the cheaper-by-the-dozen branch December 21, 2018 19:37
@palimondo
Copy link
Contributor

@jrose-apple The fix for that spurious lit test failure involving Ackermann landed 20 hours ago and @Catfish-Man requested the "test and merge" 15 hours ago… What makes you think it was that issue again? (I can now only see the latest Build failure due to invalid sha)

@jrose-apple
Copy link
Contributor

Hm. It was Ackermann that failed and the next run passed. Maybe it didn't get fixed all the way?

@palimondo
Copy link
Contributor

I'd like to make sure.

@Catfish-Man Do you have a link to that failed CI build?

@palimondo
Copy link
Contributor

@jrose-apple Could you please triple check the ERE in that fix?

@Catfish-Man
Copy link
Contributor Author

@palimondo unfortunately I don't, sorry!

@palimondo
Copy link
Contributor

@Catfish-Man All is good. @jrose-apple helped me see the error of my ways. Fix is on its way, pending CI test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants