- 
          
 - 
                Notifications
    
You must be signed in to change notification settings  - Fork 61
 
Reducing Swift Runtime overhead when useDefaultKeys keyDecodingStrategy is used #71
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
Reducing Swift Runtime overhead when useDefaultKeys keyDecodingStrategy is used #71
Conversation
| 
           Thanks for the PR. I thought Apple had tried to make protocol conformance faster a couple years ago? Is it faster now, but just still a bottlebeck? I also remember it being a little finicky to measure it properly, because things like being interposing libraries (e.g. as Xcode does at launch) could cause the optimizations not to happen. @noahsmartin In any case, this PR looks cool, and I may merge it, but I want to discuss some things. Right now, this repo is basically in maintenance mode, sort of waiting for everyone to stop using iOS 16 and below. See here: #69 . This is because I don't see further optimizations that can be made to become significantly faster than Apple's one, JSONDecoder. In fact, Apple's one is often a little faster. My recommendation, assuming that this speedup is genuine and not just for a pathological case or two (I would to need spend more time with it), is to first try to upstream it with JSONDecoder. If that becomes a big hassle, or it stalls, then I'd be more inclined to merge it, and take this repo out of maintenance mode (although Apple may just upstream it later 😓 ). But if they do merge it, then I may or may not just let this repo stay in maintenance mode and not merge the change. Are you planning to try to merge it there?  | 
    
| 
           Although, I suppose if they do make a fix, it would only apply to iOS 18+ at best? Yeah, it may be worth merging this regardless of what they do. But I want to maybe see a little more discussion on this first, and get JSONDecoder maintainers thoughts. Also, there are other optimizations you want to do, right?  | 
    
          
 Yep, you're absolutely right! Apple made dyld cache for protocol conformances in iOS 16. That's why my team and I were skeptical about this optimisations at first and then we conducted AB-testing on real users of our app. And we got statistically significant results of our optimized version of  I've already created similar PR to swift-foundation repo: https://github.com/swiftlang/swift-foundation/pull/1481/files. It will be really great if Noah or some other folks from EmergeTools could review my small performance research!  | 
    
          
 I really hope that my fix will be merged and shipped to older version of iOS 18. Surely after 18.6.2. 
 Unfortunately, It is the only optimization that I can do with ease. Removing type generic constraints here: https://github.com/michaeleisel/ZippyJSON/blob/master/Sources/ZippyJSON/ZippyJSONDecoder.swift#L834, unfortunately, won't help. My other optimization will require breaking ABI in Foundation or introducing experimental flags to stop generating   | 
    
| 
           It looks like you've had some good suggestions on the other PR. Feel free to modify this PR as needed and let me know when you think it's ready for merge. If it passes all tests, I'll be happy to merge it and cut a release. Also, just curious, how would it be backported? Does Apple do patch version releases (  | 
    
          
 I've updated both PRs. I've extracted extension to Utilities.swift here also. I think this PR is ready to be merged if you don't mind it Oh, that's tough question. I don't think Apple will consider this performance issue significant enough to create patch release...  | 
    
078def5    to
    1a3a998      
    Compare
  
    757bce3    to
    e5a0658      
    Compare
  
    e5a0658    to
    9433d0f      
    Compare
  
    | } | ||
| // testRoundTrip(of: Aa.self, json: #"{"a": {}}"#) | ||
| _testFailure(of: Aa.self, json: #"{"a": 2}"#) | ||
| _testFailure(of: Aa.self, json: #"{"a": 2}"#, relaxedErrorCheck: true) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had to do it because now zippyJSONDecoder and regular JSONDecoder hit different code paths for dictionaries
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds about right, new OS version with new error messages since the last time this was tested
| 
           I've fixed one test, applied new code-style. Now, for sure, PR is ready to be merged)  | 
    
| 
           Thank you! I'll make a branch with a stylistic change to this one, and merge it. A few side notes around this stuff: 
  | 
    
| @@ -0,0 +1,11 @@ | |||
| # .editorconfig — 4-space indents by default | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You caught it right as I'd force-pushed to master 😅
| 
           Merged via #72  | 
    
Shortly, the larger app is, the more expensive protocol conformance checking is.
For detailed explanation see: https://forums.swift.org/t/improving-jsondecoder-encoder-performance-for-large-apps/81839/
I ran JSONBenchmark in Swift repo with my changes in ZippyJSONDecoder and without - there is barely any difference. At most 1% boost for some percentile and 1% degradation in other percentiles
But when I check performance using my own benchmark that is oriented on measuring Swift Runtime overhead - https://github.com/ChrisBenua/JSONDecoderEncoderBenchmarks
Here are results:
Mode: Regular CodingKeys decode_zippy
Median: 6.533840
0.25 Quantile: 6.524136
0.75 Quantile: 6.560604
Mode: Regular CodingKeys decode_zippy_new
Median: 2.767571
0.25 Quantile: 2.766739
0.75 Quantile: 2.770014
So the newer version has significantly less Swift Runtime overhead