Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BinaryPlistEncoder

A faster drop in replacement for PropertyListEncoder and PropertyListDecoder on binary property lists. Output is plain bplist00, byte compatible with everything that already reads property lists, so a document written here still opens in any earlier version of the app that wrote it with Foundation.

let data = try BinaryPlistEncoder().encode(project)
let project = try BinaryPlistDecoder().decode(Project.self, from: data)

Existing Codable conformances are used unchanged — nothing in the model layer has to be rewritten.

Why it is faster

PropertyListEncoder makes three passes over the data. Codable reflection builds a complete intermediate NSDictionary / NSArray / NSData tree, CFPropertyListCreateData walks that tree and uniques every object in it through a hash set — hashing and comparing every message body — and only then writes bytes.

This encoder appends values to a flat object table as they are encoded and writes the byte stream from it in one pass. Strings are uniqued, because header names, hosts and methods repeat constantly. Data blobs are uniqued as well, but keyed on their length together with their first and last 32 bytes rather than on every byte, so repeated message bodies cost one exact comparison instead of a full hash of every body in the document. On a real 76 MB capture that keeps the file within 1% of what PropertyListEncoder produces, at about 2% of the encoding time.

Decoding skips the object tree entirely, reading values straight out of the byte buffer, and compares dictionary keys against raw bytes rather than allocating a String for every key it passes over.

Measurements

swift run -c release BinaryPlistBenchmark generates a synthetic document shaped like a Proxygen project and measures the full Codable path. On an M-series Mac, 20 000 messages with 2 KB inline bodies, a 57 MB property list:

PropertyListEncoder/Decoder BinaryPlistEncoder/Decoder
Encode 655 ms 282 ms 2.3x
Decode 1.12 s 239 ms 4.7x

Those bodies are random, so nothing dedupes and the uniquing lookup is pure cost. A real 76 MB capture, where bodies do repeat, comes out at 2.7x encoding and 4.9x reading.

The benchmark also reports the Codable walk on its own — 90 ms here — which is the floor no encoder built on Encoder can go below. Roughly a third of the remaining encode time is protocol dispatch that belongs to Codable rather than to serialization.

Pass a path to a real project.plist to measure against actual data instead:

swift run -c release BinaryPlistBenchmark --iterations 10 path/to/project.plist

That mode reads and rewrites the document as an untyped PlistValue tree, so it needs no model types.

Compatibility

The details that have to match Foundation exactly, all covered by differential tests that compare the decoded trees of both encoders' output:

  • encodeNil() writes the string $null, the placeholder PropertyListEncoder uses, and the decoder reads it back as nil
  • dates are written as binary property list dates, seconds since 2001, not as numbers
  • integers use the same 1, 2, 4 and 8 byte forms, with negatives always in the 8 byte form
  • strings are ASCII when every scalar is below 128, otherwise UTF-16BE
  • Data and Date take precedence over their Codable conformances, as in Foundation

The decoder accepts everything CoreFoundation writes, including 16 byte integers, floats, sets and UIDs, and it throws rather than trapping on truncated or corrupted input.

License

Apache License 2.0. See LICENSE.

About

A faster drop-in replacement for PropertyListEncoder and PropertyListDecoder on binary property lists, written in Swift.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages