Skip to content

Configuration

Every optimisation that has a plainer way of doing the same thing is behind a switch, with the plain way kept beside it and producing the same bytes. They are macros, defined in serpent/config.hpp, and CMake options of the same names.

Switch Default On, it Off, it Costs
SERPENT_USE_ZMIJ on in CMake, off in the headers writes reals with the bundled Żmij uses std::to_chars one source file to build; 67 KB of code on arm64
SERPENT_ZMIJ_OPTIMIZE_SIZE AUTO builds Żmij without its table of powers of ten keeps the table 36 KB rather than 67 KB, and a real takes about a quarter longer to write. AUTO lets Żmij choose, which it does by whether the compiler is optimising for size
SERPENT_BOUNDED_OBJECT_WRITE on writes runs of number and boolean members into room claimed once asks for room per key and per value nothing but the code
SERPENT_INTEGER_TABLE 1 1: integers two digits at a time from a 400-byte table. 2: four at a time from a 40 KB one 0: std::to_chars the table
SERPENT_USE_FAST_FLOAT on reads reals in one walk with the bundled fast_float checks the grammar, then std::from_chars, which needs a standard library that has it for floating point 28 KB of code and tables on arm64 - largely a second copy, where the standard library's own from_chars is built on fast_float, as libstdc++'s is - and a large header to compile
SERPENT_FORCE_INLINE on marks the few small functions on the path of every value always-inline leaves inlining to the compiler, whose budget for it is per translation unit and runs out in a large one - the same source then runs two to four times slower somewhat more code where those functions are used
SERPENT_WIDE_STRING_SCAN on finds what a string must escape eight bytes at a time looks at every byte nothing but the code

The CMake target serpent::plain is the library with every one of them off, whatever the build chose: headers only, and the standard library throughout.

What each is worth is in Benchmarks, and tools/bench.sh --ablations measures the default with each turned off on its own.

A switch changes how fast a document is produced or read, never what it is, and that is tested: each way of writing an integer or a real, reading a real, or scanning a string is compared with the plain way over millions of values.

Żmij, Glaze's integer formatting and fast_float are other people's work, kept under include/serpent/external/ with their licences and copied from pinned upstream versions by tools/vendor-*.sh; the README there says who wrote what.

How deep a document may nest

SERPENT_MAX_DEPTH, 32 unless set, is how deep any reader or writer goes, counting the root as 0. A document nested deeper is refused by validate, decode and try_decode alike, rather than recursed into: reading into a recursive type of your own costs stack at every level, so a task with a small stack may want it lower. A serpent::value tree takes the same couple of kilobytes at any depth, from any of the formats. It can be from 1 to 32, since a writer remembers which levels are objects in one 32-bit word. Set it as a CMake cache variable, which puts it on every target that links serpent; every translation unit in a program must agree on it.