Repository navigation
The performance release. Against 0.9.0, measured on the x86 benchmark gate:
| 0.9.0 | 0.10.0 | ||
|---|---|---|---|
parse_email |
0.450 ms | 0.231 ms | 1.95x |
parse_many |
3.542 ms | 1.926 ms | 1.84x |
parse_email_tree |
0.419 ms | 0.232 ms | 1.81x |
| full read incl. attachments | 0.457 ms | 0.250 ms | 1.83x |
mode="lazy", nothing read |
0.071 ms | 0.046 ms | 1.55x |
| quoted-printable body | 0.342 ms | 0.226 ms | 1.51x |
| re-reading an attachment | — | — | 81x |
| re-reading the header map | — | — | 38x |
The last two are not micro-optimisations: attachment.content and mail.headers used to redo the
decode on every access and now publish one cached object, so the second read does no work at all.
Against other libraries on the same message and the same result — subject, both body lists,
attachments decoded — fast_mail_parser is now 42x faster than mail-parser and 62x faster
than the stdlib email package.
One behaviour change
Lazy modes borrow the payload instead of copying it (#239). A deferred attachment or MIME-tree
leaf no longer copies its encoded bytes out of the message; it keeps offsets into the payload you
passed in, and keeps that payload alive for as long as it is reachable. Parsing a 96 MiB
single-attachment message in mode="lazy" went from 192 MiB of peak memory to 96 MiB — but holding
one attachment now holds its message. If you want the bytes without the message, read content,
which is a decoded copy, and drop the attachment. Nothing in the API changed.
Where the speed came from
- Payloads are borrowed, not copied (#226) —
strpayloads joinedbytes, so a batch of ten
thousand messages no longer duplicates every one of them. - Attachments and tree leaves stop copying on every read (#227), and
headersis built once per
object and shared (#231). - Base64 decodes with SIMD (#228) and quoted-printable a run at a time (#229), both in the
vendored mailparse, withdata_encodingkept as the arbiter of anything the SIMD decoder turns
down. parse_manysizes its workers by bytes and the calling thread is one of them (#232).- A part's body is evaluated once (#230), and the header table allocates half as much (#238).
Correctness and maintenance
The parsing core is now its own crate with its own Rust tests (#236); the vendored mailparse delta
is a CI invariant that re-applies the patch on every run (#235); the MIME tree has one traversal
instead of two (#237) and the flat parsers share one envelope reader and one part classifier (#234);
the binding is split into modules (#233). Code placement and PGO can now be measured rather than
argued about (#240, #241).
Full changelog: https://github.com/namecheap/fast_mail_parser/blob/main/CHANGELOG.md
Compare: v0.9.0...v0.10.0