Skip to content

perf(storage): Parse JSON from a slice instead of a reader - #898

Merged
JeanMertz merged 1 commit into
mainfrom
improve-grep
Jul 30, 2026
Merged

perf(storage): Parse JSON from a slice instead of a reader#898
JeanMertz merged 1 commit into
mainfrom
improve-grep

Conversation

@JeanMertz

@JeanMertz JeanMertz commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Every JSON read went through serde_json::from_reader over a
BufReader. Its IoRead advances one byte at a time, which costs the
parser both its memchr scans and its zero-copy string borrows — the
crate's own docs note that reading the file into memory and calling
from_slice is usually faster. Reading whole and parsing the slice cut
jp conversation grep --scope chat from 2.5s to 1.8s on a 600-turn
workspace, with JSON parsing dropping from roughly 42% of working
samples to under 3%.

Three call sites carried the slow path: load_json, which loads
conversation metadata and event streams; load_count_and_timestamp_events,
which skims an event file for its count and last timestamp; and
get_expiring_timestamp, which reads just expires_at out of a metadata
file. jp_storage::value::read_json shared the pattern and moves too,
though nothing hot calls it.

The tradeoff is peak memory: a file is now fully resident while it parses,
where the reader streamed it. Conversation event files are the largest
thing this reads and they are already deserialized into memory in full,
so the extra transient allocation is bounded by a file that was going to
be resident anyway.

@JeanMertz JeanMertz changed the title enhance(cli, term): Fit grep hits to terminal width feat(cli, term): Make conversation grep hits addressable and scriptable Jul 28, 2026
@JeanMertz JeanMertz changed the title feat(cli, term): Make conversation grep hits addressable and scriptable feat(cli, conversation): Make conversation grep hits addressable Jul 29, 2026
Every JSON read went through `serde_json::from_reader` over a
`BufReader`. Its `IoRead` advances one byte at a time, which costs the
parser both its `memchr` scans and its zero-copy string borrows — the
crate's own docs note that reading the file into memory and calling
`from_slice` is usually faster. Reading whole and parsing the slice cut
`jp conversation grep --scope chat` from 2.5s to 1.8s on a
600-conversation workspace, with JSON parsing dropping from roughly 42%
of working samples to under 3%.

Three call sites carried the slow path: `load_json`, which loads
conversation metadata and event streams;
`load_count_and_timestamp_events`, which skims an event file for its
count and last timestamp; and `get_expiring_timestamp`, which reads just
`expires_at` out of a metadata file. `jp_storage::value::read_json`
shared the pattern and moves too, though nothing hot calls it.

The tradeoff is peak memory: a file is now fully resident while it
parses, where the reader streamed it. Conversation event files are the
largest thing this reads and they are already deserialized into memory
in full, so the extra transient allocation is bounded by a file that was
going to be resident anyway.

Signed-off-by: Jean Mertz <git@jeanmertz.com>
@JeanMertz JeanMertz changed the title feat(cli, conversation): Make conversation grep hits addressable perf(storage): Parse JSON from a slice instead of a reader Jul 30, 2026
@JeanMertz
JeanMertz merged commit 39d9c41 into main Jul 30, 2026
16 checks passed
@JeanMertz
JeanMertz deleted the improve-grep branch July 30, 2026 04:58
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