Skip to content

Conversation

@ChrisBenua
Copy link
Contributor

Shortly, casting like let dictType = type as? StringDecodableDictionary.Type is extremely slow in big applications (more specifically, for applications with large swift5_proto section).

And we definitely can avoid this costly cast when options.keyDecodingStrategy is useDefaultKeys.

For more details see: https://forums.swift.org/t/improving-jsondecoder-encoder-performance-for-large-apps/81839

This overhead is insignificant when benchmarking, because of launching the same decoding for 1000 times but overhead is introduced only for the first cast for each pair (class/struct/enum, protocol). Then Swift Runtime uses internal cache.

That's why I have my own benchmark where I relaunch my app after all my models were encoded at most once:
https://github.com/ChrisBenua/JSONDecoderEncoderBenchmarks

I've opened almost identical PR to ZippyJSON: michaeleisel/ZippyJSON#71

Also I've run ReerJSONBenchmark with small modifications so I can compare current version of ReerJSONDecoder with version from this PR:

So there is almost no difference when using 1000 iterations. I've tested on iPhone 13 iOS 18.5.

📊 GitHub Events Benchmark Results Summary

Decoder Decodes Per Second Average Time (ms) Relative Speed

ReerJSONV2 391.15 ops 2.557 ms 1.19x
ReerJSON 390.04 ops 2.564 ms 1.18x
Foundation JSONDecoder 329.59 ops 3.034 ms 1.00x

📊 Twitter Benchmark Results Summary

Decoder Decodes Per Second Average Time (ms) Relative Speed

ReerJSONV2 493.26 ops 2.027 ms 2.29x
ReerJSON 488.18 ops 2.048 ms 2.27x
Foundation JSONDecoder 215.32 ops 4.644 ms 1.00x

📊 Apache Builds Benchmark Results Summary

Decoder Decodes Per Second Average Time (ms) Relative Speed

ReerJSON 1166.55 ops 0.857 ms 2.04x
ReerJSONV2 1163.01 ops 0.860 ms 2.03x
Foundation JSONDecoder 571.94 ops 1.748 ms 1.00x

📊 Canada Geography Benchmark Results Summary

Decoder Decodes Per Second Average Time (ms) Relative Speed

ReerJSON 15.35 ops 65.163 ms 1.16x
ReerJSONV2 13.41 ops 74.545 ms 1.02x
Foundation JSONDecoder 13.18 ops 75.847 ms 1.00x

📊 Random Data Benchmark Results Summary

Decoder Decodes Per Second Average Time (ms) Relative Speed

ReerJSONV2 152.46 ops 6.559 ms 1.97x
ReerJSON 151.82 ops 6.587 ms 1.97x
Foundation JSONDecoder 77.25 ops 12.945 ms 1.00x

@Asura19
Copy link
Member

Asura19 commented Sep 7, 2025

@ChrisBenua Great job, I've also found the performance issue here two weeks ago when I was looking for the performance gap with ZippyJSON. However, both Foundation and ZippyJSON handle it this way, and it seems that this is the only way at present. I struggled all day but couldn't find a possible solution and eventually gave up.
Your modification cleverly bypasses this problem in some scenarios, which will indeed improve the performance. Thank you very much for your suggestion.

I only have one suggestion about the extension:

  • Use @inline(__always)
  • Using default in switch case to cover all non-default cases, with newly added strategies automatically categorized as non-default, making it simpler.
  • And put the extension into Utilities.swift file.
extension JSONDecoder.KeyDecodingStrategy {
    @inline(__always)
    var isDefault: Bool {
        switch self {
        case .useDefaultKeys:
            return true
        default:
            return false
        }
    }
}

@Asura19 Asura19 merged commit cd4b17e into reers:main Sep 8, 2025
1 check passed
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.

2 participants