Skip to content

v0.10.0

Latest

Choose a tag to compare

@kurok kurok released this 18 Sep 14:20
· 8 commits to master since this release
Immutable release. Only release title and notes can be modified.
49a241c

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) — str payloads joined bytes, 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 headers is 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, with data_encoding kept as the arbiter of anything the SIMD decoder turns
    down.
  • parse_many sizes 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