Skip to content

Buffer reads in ElfZipFileChannel - #153

Open
shubh73 wants to merge 1 commit into
meta:mainfrom
shubh73:elfzip-buffered-reads
Open

shubh73 wants to merge 1 commit into
meta:mainfrom
shubh73:elfzip-buffered-reads

Conversation

@shubh73

@shubh73 shubh73 commented Oct 19, 2026 •

Copy link
Copy Markdown

MinElf reads ELF fields a few bytes at a time, and library names one byte at a time. ElfZipFileChannel passed each of those reads straight to the entry's InputStream, so resolving the dependencies of libreactnative.so took 178 stream reads and 85 skips.

This serves reads from an 8 KiB buffer and only touches the stream to refill it. The same lookup now takes 20 reads and 18 skips. DirectSplitSoSource uses the same channel, so it benefits too.

Since read(ByteBuffer, long) is rewritten anyway, it now also:

  • loops when InputStream.read or skip return fewer bytes than asked;
  • copies into the backing array at the buffer's position instead of at offset 0.

Neither case is reachable today. Both callers only open stored entries, whose streams return full counts in practice, and MinElf always reads into a fresh buffer at position 0.

Test plan

Same setup as #152. Median time to full display, from one run with all four builds side by side (R8 off, the template default):

APK main #152 #153 Both
482 entries 366 ms 356 ms 346 ms 330 ms
5,482 entries 422 ms 357 ms 428 ms 350 ms

Against main, this PR is −3.6% (−6.7% to −0.3%) on the small APK and −1.5% (−3.7% to +0.9%) on the large one. On top of #152 it's −4.0% (−7.5% to −0.6%) and −1.0% (−3.8% to +2.1%). On the large APK, the repeated central-directory parse that #152 removes dominates, so this change mostly shows on the small one. These are estimated over all launches with 95% confidence intervals.

Repeating the run with R8 on gave the same picture: both PRs together measured −5.3% (−8.3% to −2.5%) and −13.4% (−16.3% to −10.7%) against main.

On the JVM, I fuzzed read(ByteBuffer, long) with 8,000 random reads over stored and deflated entries, and 1,200 more against streams that never skip and return at most 7 bytes per read. Every byte, return value and position matched the entry; the current code fails this. MinElf returns the same dependencies for all 10 libraries in the APK, stored and deflated, matching llvm-readelf -d.

🤖 Generated with Claude Code

MinElf reads ELF fields a few bytes at a time and moves back and forth between
the headers, the dynamic section and the string table. ElfZipFileChannel
served each of those reads straight from the entry's InputStream, so resolving
one library's dependencies took hundreds of tiny stream reads.

Serve reads from an 8 KiB buffer instead. This speeds up resolving library
dependencies in both DirectApkSoSource and DirectSplitSoSource.

It also fixes latent issues in read(ByteBuffer, long), which MinElf doesn't
trigger today:
- It ignored short reads and skips from the underlying stream.
- It copied into the backing array at offset 0 instead of at the buffer's
  position.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant