Skip to content

Introduce a small internal buffer for I/O. - #81

Merged
kinetiknz merged 4 commits into
masterfrom
buffered_io
Apr 22, 2026
Merged

Introduce a small internal buffer for I/O.#81
kinetiknz merged 4 commits into
masterfrom
buffered_io

Conversation

@kinetiknz

Copy link
Copy Markdown
Collaborator

Rework nestegg_io with a new ne_io wrapper that provides an 8KB internal buffer to significantly reduce the I/O callback overhead for single-byte reads from the EBML and lacing parsers.

nestegg_packet_end_offset is added to provide an accurate packet end offset based on the parser's logical stream position since the buffered I/O can cause the raw stream position to be further ahead. This is intended to replace Gecko's Tell() after a nestegg_read_packet with an API at the appropriate abstraction level.

It's tricky to give a definitive performance improvement ratio for this change, since it's so dependent on the cost of the user-supplied I/O read callback. Testing locally with a trivial read callback, there's around a 1.4x improvement for typical files and up to 7x improvement for crafted files that trigger many single-byte reads. Gecko's read callback is fairly complex, with differing performance based on lock contention and caching, but simulating a moderately expensive read callback (by adding a CPU yield to the trivial callback mentioned earlier) shows a 4x improvement for typical files and 49x for crafted files.

@kinetiknz
kinetiknz requested a review from ashleyz April 1, 2026 09:27
@kinetiknz kinetiknz self-assigned this Apr 1, 2026

@ashleyz ashleyz left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Comment thread include/nestegg/nestegg.h
Comment thread src/nestegg.c
Wrap nestegg_io in an internal ne_io struct and change all pure I/O
function signatures from nestegg_io * to ne_io *.  This separates
I/O state from parser state, so I/O functions cannot access the full
nestegg context.  Purely mechanical, no behavioral change.
Capture the logical stream position after each packet is fully read and
expose it via nestegg_packet_end_offset().  For SimpleBlock packets this
is the end of the block; for BlockGroup packets this is the end of the
entire group.  This lets callers query the stream position without
relying on the raw I/O tell callback, which may not reflect the logical
position when internal buffering is active.
Change nestegg_io read callback to return number of bytes read, allowing
short reads.  nestegg now uses an internal 8KB buffer to service reads
internally, refilling the buffer via the updated read callback.  This
significantly reduces I/O callback overhead for the many single-byte
reads the EBML and lace parsers require.

The max_offset parse fence is enforced during init only; it is cleared
after init succeeds so that subsequent cue loading, seeking, and packet
reading can access the full stream.

Refactor the test suite to run all tests through legacy read, buffered
readn, and short-read (16 byte cap) readn I/O paths.  Add a
seek-then-read test and a max_offset fencing test.  Enable sanitizer
halt_on_error in the test harness.
@kinetiknz
kinetiknz merged commit 405fdae into master Apr 22, 2026
2 checks passed
@kinetiknz
kinetiknz deleted the buffered_io branch April 22, 2026 20:20
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.

2 participants