Plast interview

18. Errors, Fast

In a nanosecond-sensitive parser, error paths must stay cold while success remains branch-predictable and register-resident. You need an API that propagates parse failures without inflating code size or cache footprint. Choose an approach that preserves throughput under bursty market data.

bool parse_rc(std::string_view s, int& out) noexcept { if(s.empty()||s[0]<'0'||s[0]>'9') return false; out=s[0]-'0'; return true; }
std::optional<int> parse_opt(std::string_view s) noexcept { if(s.empty()||s[0]<'0'||s[0]>'9') return {}; return s[0]-'0'; }

Part 1.

Which API would you expose on the hottest path and why? Discuss expected codegen, ABI costs, branch behavior, and cache footprint given rare errors.

Part 2.

(1) How does noexcept influence codegen and inlining for these functions?

(2) Would you mark return values [[nodiscard]]? Why or why not?

(3) Compare ABI costs: bool+out-param vs std::optional<int> return.

(4) When prefer std::expected<int, Err> over std::optional<int> here?

(5) How do status object size and alignment affect cache and IPC?

Answer

Answer (Part 1)

For the hottest path, prefer bool parse_rc(..., int& out) noexcept. It typically returns a single bool in a register and writes the result once, minimizing tag manipulation and code size; the success path is straight-line and predictable. std::optional<int> can be near-zero-cost for trivial int, often using registers for value+flag, but may increase caller code size and checks, impacting I-cache and branch footprints. In both cases, keep it noexcept and measure; pick optional only if ergonomic clarity outweighs marginal overhead on your target.

Answer (Part 2)

(1) noexcept lets the compiler assume no unwinding, enabling tighter control flow and more aggressive inlining. It can remove hidden exception edges, improving optimization and code size.

(2) Yes, to prevent silently ignored failures. [[nodiscard]] on bool and std::optional<int> catches accidental drops that become latent correctness bugs.

(3) bool+out-param usually returns one register and passes an address for the out value. std::optional<int> often uses registers for tag+value but can enlarge caller code.

(4) Prefer expected when you need structured error information beyond presence/absence. With small Err, expected<int, Err> can match optional performance.

(5) Larger, misaligned status objects spill from registers and add loads/stores, hurting IPC. They also bloat I/D-cache footprint, increasing misses under burst load.