Skip to content

feat(execution-context): reset() to reuse one context across inputs - #26

Merged
pigri merged 3 commits into
masterfrom
perf/execution-context-reuse
Aug 16, 2026
Merged

pigri merged 3 commits into
masterfrom
perf/execution-context-reuse

Conversation

@pigri

@pigri pigri commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Why

An ExecutionContext holds one value slot per scheme field, and allocating
and zeroing that table dominates the cost of filling one. Measured on an
84-field scheme:

ExecutionContext::new 316 ns
clear() on an existing one 52 ns

That 316 ns is more than every field write in a typical bind combined, and it
is paid before the first one.

clear() already keeps the allocation, but it cannot help a caller in a loop:
the context's 'e is fixed to the data it was last filled from, so the next
input — borrowed from somewhere else — will not typecheck against it. Callers
in that shape (one context per packet, per request, per row) end up allocating
a fresh context every time.

What

reset() clears the context and re-labels it for a new borrow lifetime, so
one allocation can be handed from input to input:

let mut spare = ExecutionContext::new(&scheme);
for input in inputs {
    let mut ctx = spare.reset();
    ctx.set_field_value(field, &input.value)?;
    // ... execute filters against ctx ...
    spare = ctx.reset();
}

In the caller this PR was written for — a per-packet firewall bind over an
84-field scheme — this takes binding a packet from 616 ns to 439 ns (−29%)
with no other change.

Soundness

clear() has already replaced every value with None, dropping each
LhsValue<'e>, and cleared the list matchers. The remaining members do not
borrow from 'e: scheme is owned, list_matchers are 'static trait
objects, and user_data is U. With nothing reachable that borrows from
'e, re-labelling the lifetime cannot produce a dangling reference, and the
two types differ only in that parameter, so their layout is identical.

test_reset_reuses_one_context_across_borrows exercises the case that would
be UB if this were wrong — each iteration borrows from a String that is
dropped before the next iteration begins — and it passes under Miri with
-Zmiri-strict-provenance.

pigri added 3 commits August 16, 2026 01:55
An ExecutionContext holds one value slot per scheme field, and
allocating and zeroing that table dominates the cost of filling one:
for an 84-field scheme it is ~300 ns, more than every field write
combined and paid before the first one.

clear() already keeps the allocation, but it cannot help a caller in a
loop: the context's 'e is fixed to the data it was last filled from, so
the next input -- borrowed from somewhere else -- does not typecheck
against it. Callers in that shape (one context per packet, per request,
per row) therefore allocate a fresh context every time.

reset() clears the context and re-labels it for a new borrow lifetime,
so the allocation can be handed from input to input:

    let mut spare = ExecutionContext::new(&scheme);
    for input in inputs {
        let mut ctx = spare.reset();
        ctx.set_field_value(field, &input.value)?;
        // ... execute filters ...
        spare = ctx.reset();
    }

Sound because clear() has already dropped every LhsValue<'e> and the
remaining members do not borrow from 'e; the two types differ only in
that lifetime parameter. Covered by a test that borrows from a value
which dies before the next iteration -- the case that would be UB if
the re-labelling were wrong -- and it passes under Miri with
-Zmiri-strict-provenance.
byte_char_slices on the wildcard-replace flag test and the
truncate(0)-instead-of-clear in the panic hook. Both are new lints on
current stable, both fail the -D warnings gate on any PR, and neither
changes behaviour.
tombi format --check has been failing on this file for every PR: it
writes target table keys with double quotes. Cargo accepts either form
and the dependency graph is unchanged (cargo metadata and a
wirefilter-ffi check both still resolve the cfg(unix) dev-dependency).
@pigri
pigri merged commit 940c89f into master Aug 16, 2026
8 checks passed
@pigri
pigri deleted the perf/execution-context-reuse branch August 16, 2026 11:01
@linear-code

linear-code Bot commented Aug 16, 2026

Copy link
Copy Markdown

SYN-64

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant