Skip to content

Enhance bufferedWriter with bufio.Writer, scratch space, and configurable thresholds (B1)#2321

Open
AdamDrewsTR wants to merge 3 commits into
qax-os:masterfrom
AdamDrewsTR:perf/buffered-writer
Open

Enhance bufferedWriter with bufio.Writer, scratch space, and configurable thresholds (B1)#2321
AdamDrewsTR wants to merge 3 commits into
qax-os:masterfrom
AdamDrewsTR:perf/buffered-writer

Conversation

@AdamDrewsTR
Copy link
Copy Markdown
Contributor

Summary

Enhances the internal bufferedWriter used by StreamWriter to reduce syscalls and heap allocations during streaming worksheet generation. Adds two new Options fields to give callers control over when and how streaming data spills to disk.

Changes

bufio.Writer layer after threshold

Once the in-memory buffer reaches the threshold, the old code flushed raw bytes.Buffer to the temp file on every Sync() call. The new code switches to a bufio.Writer wrapping the temp file, so disk writes happen only when the bufio buffer is full (default 128 KiB). This reduces write syscalls by ~100x for large worksheets (e.g. 100 MB XML goes from ~3000 syscalls to ~400).

After the switch, the bytes.Buffer backing array is released entirely (bw.buf = bytes.Buffer{}) to avoid holding two large buffers simultaneously.

Scratch space for numeric formatting

A [24]byte scratch field eliminates heap allocations in the new WriteInt, WriteUint, and WriteFloat methods by using strconv.AppendInt/AppendUint/AppendFloat directly into stack memory.

Offset tracking and WriteAt

The written field tracks total bytes written. WriteAt allows updating data at specific offsets — in-memory mode modifies the buffer directly, temp file mode flushes bufio first then uses pwrite.

CopyTo for efficient streaming to ZIP

CopyTo copies all buffered data to an io.Writer using a large read buffer (256 KiB minimum, or bioSize if larger) to minimize Pread syscalls when writing sheet data into the ZIP archive.

Configurable options

  • StreamingChunkSize: Controls when streaming spills to a temp file. Default is StreamChunkSize (16 MiB). Set to -1 to keep all data in memory (eliminates disk I/O when sufficient RAM is available).
  • StreamingBufSize: Controls the bufio.Writer size after spill. Default is StreamingBufSizeDefault (128 KiB), which is the measured inflection point on both NVMe and HDD.

Compatibility

All existing tests pass. The enhanced bufferedWriter is a strict superset of the old one — existing callers that only use Write, WriteString, Reader, Sync, Flush, and Close are unaffected. The new methods (WriteInt, WriteUint, WriteFloat, WriteAt, CopyTo, Bytes, Reset) are additive.

…able thresholds

- Add bufio.Writer layer after temp file threshold is crossed, reducing disk
  write syscalls from per-Write to per-buffer-full (default 128 KiB)
- Add scratch [24]byte field for strconv.Append* to avoid heap allocations
  in WriteInt, WriteUint, and WriteFloat helper methods
- Track total bytes written for offset-based operations (WriteAt)
- Add WriteAt method for updating data at specific offsets in both in-memory
  and temp file modes
- Add CopyTo method with large read buffer to minimize Pread syscalls when
  copying data to ZIP writers
- Add Bytes method for direct access to in-memory buffer contents
- Add Reset method for clearing state without closing the temp file
- Add StreamingChunkSize option to control when streaming spills to disk
  (0 = default 16 MiB, -1 = never spill, keeps all data in memory)
- Add StreamingBufSize option to control bufio.Writer size aft- Add StreamingBufSize option to control bufio.Writer size aft- Add StreamingBufSize option to control bufio.Writer size aft- Add Streaminak memory usage during streaming writes
@AdamDrewsTR AdamDrewsTR changed the title Enhance bufferedWriter with bufio.Writer, scratch space, and configurable thresholds Enhance bufferedWriter with bufio.Writer, scratch space, and configurable thresholds (2) May 12, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.60%. Comparing base (4bebb61) to head (eeb75bb).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #2321   +/-   ##
=======================================
  Coverage   99.60%   99.60%           
=======================================
  Files          32       32           
  Lines       26791    26876   +85     
=======================================
+ Hits        26685    26770   +85     
  Misses         55       55           
  Partials       51       51           
Flag Coverage Δ
unittests 99.60% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@AdamDrewsTR AdamDrewsTR changed the title Enhance bufferedWriter with bufio.Writer, scratch space, and configurable thresholds (2) Enhance bufferedWriter with bufio.Writer, scratch space, and configurable thresholds (2\1B) May 12, 2026
@AdamDrewsTR AdamDrewsTR changed the title Enhance bufferedWriter with bufio.Writer, scratch space, and configurable thresholds (2\1B) Enhance bufferedWriter with bufio.Writer, scratch space, and configurable thresholds (1B) May 12, 2026
@AdamDrewsTR AdamDrewsTR changed the title Enhance bufferedWriter with bufio.Writer, scratch space, and configurable thresholds (1B) Enhance bufferedWriter with bufio.Writer, scratch space, and configurable thresholds (B1) May 12, 2026
Cover WriteInt, WriteUint, WriteFloat, Bytes, WriteAt, CopyTo, and
Reset methods in both in-memory and temp-file (bio) code paths.
Test NewStreamWriter with custom StreamingChunkSize (-1, positive)
and StreamingBufSize options.
@xuri xuri added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label May 13, 2026
Add tests for IO error scenarios when the underlying temp file is
closed or broken:
- TestBufferedWriterWriteAtFlushError: Flush fails in WriteAt
- TestBufferedWriterCopyToFlushError: Flush fails in CopyTo
- TestBufferedWriterCopyToSeekError: Seek fails in CopyTo
- TestBufferedWriterSyncWriteToError: WriteTo fails in Sync

This brings diff coverage from 95.56% to 100%.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants