Major refactoring of Darwin Foundation's (and hence swift-foundation's) JSONEncoder took place a couple of years ago, with the rewrite being entirely in Swift, no longer relying on NSJSONSerialization. This effort resulted in a significant performance boost on Darwin. However, when swift-corelibs-foundation was re-cored on top of swift-foundation in 6.0, a performance regression was discovered there compared to the 5.10 toolchain.
After examining the code from the 5.10 toolchain's JSONEncoder, I've adapted the swift-foundation implementation to use its general architecture, with some additional important correctness fixes and other adjustments. I also included some other optimizations on top of it. All of this resulted in slightly improved performance in usage within the toolchain (over 5.10), as well via the swift-foundation package and Darwin Foundation.framework over previous.
To summarize the various changes:
Serialize to a [UInt8] instead of a Data. It's unfortunately noticeably more expensive to serialize to Data right now.
Avoid unnecessary String allocations when serializing escaped characters in Strings.
Replace JSONReference (mutable reference / leaf value combination) with JSONEncoderValue (immutable value type) + JSONFuture (container of either a value or one of the reference types) + JSONFuture.RefArray + JSONFuture.RefObject (pure reference types). This approach resulted in less overall retain/release traffic, at the cost of some rare "unwrapping" of JSONEncoderValue values back into the reference types.
Make each __JSONEncoder responsible for one layer of decoding, instead of reusing the same one multiple times. This enabled some performance optimizations over what the 5.10 SCL-F implementation had, including CodingKey and CodingPath optimizations (each encoder holds a CodingKey, and each nested container has a CodingPathNode linked list descending from the original encoder; the path, when needed (rarely) is constructed by walking up this chain of encoders), and __JSONEncoder instance reuse to avoid allocation churn.
Reimplemented the _JSONDirectArrayEncodable optimization path to use [UInt8] buffers directly (with substring lengths where needed) instead of holding on to Strings. This will enable us to have the stdlib encode straight to our buffer instead of jumping through an unnecessary transient String allocation if such an API is ever introduced.
Major refactoring of Darwin Foundation's (and hence swift-foundation's)
JSONEncoder
took place a couple of years ago, with the rewrite being entirely in Swift, no longer relying onNSJSONSerialization
. This effort resulted in a significant performance boost on Darwin. However, when swift-corelibs-foundation was re-cored on top of swift-foundation in 6.0, a performance regression was discovered there compared to the 5.10 toolchain.After examining the code from the 5.10 toolchain's
JSONEncoder
, I've adapted the swift-foundation implementation to use its general architecture, with some additional important correctness fixes and other adjustments. I also included some other optimizations on top of it. All of this resulted in slightly improved performance in usage within the toolchain (over 5.10), as well via the swift-foundation package and Darwin Foundation.framework over previous.To summarize the various changes:
[UInt8]
instead of aData
. It's unfortunately noticeably more expensive to serialize toData
right now.String
allocations when serializing escaped characters inString
s.JSONReference
(mutable reference / leaf value combination) withJSONEncoderValue
(immutable value type) +JSONFuture
(container of either a value or one of the reference types) +JSONFuture.RefArray
+JSONFuture.RefObject
(pure reference types). This approach resulted in less overall retain/release traffic, at the cost of some rare "unwrapping" ofJSONEncoderValue
values back into the reference types.__JSONEncoder
responsible for one layer of decoding, instead of reusing the same one multiple times. This enabled some performance optimizations over what the 5.10 SCL-F implementation had, includingCodingKey
andCodingPath
optimizations (each encoder holds aCodingKey
, and each nested container has aCodingPathNode
linked list descending from the original encoder; the path, when needed (rarely) is constructed by walking up this chain of encoders), and__JSONEncoder
instance reuse to avoid allocation churn._JSONDirectArrayEncodable
optimization path to use[UInt8]
buffers directly (with substring lengths where needed) instead of holding on toString
s. This will enable us to have the stdlib encode straight to our buffer instead of jumping through an unnecessary transientString
allocation if such an API is ever introduced.