Skip to content

This closes #2121, use mmap to open spreadsheet file#2331

Open
artur-chopikian wants to merge 1 commit into
qax-os:masterfrom
artur-chopikian:mmap-readall
Open

This closes #2121, use mmap to open spreadsheet file#2331
artur-chopikian wants to merge 1 commit into
qax-os:masterfrom
artur-chopikian:mmap-readall

Conversation

@artur-chopikian
Copy link
Copy Markdown
Contributor

@artur-chopikian artur-chopikian commented May 18, 2026

Description

Replace os.Open with golang.org/x/exp/mmap.Open in OpenFile. The returned *mmap.ReaderAt implements io.ReaderAt, so it is passed unchanged to the existing openReaderAt path — no downstream changes are required, and the public OpenFile signature and behaviour stay the same.

With memory mapping, the operating system page-maps the .xlsx file into the process address space on demand. archive/zip then serves section reads directly from the kernel page cache instead of copying through buffered read syscalls into Go heap buffers. The mapping is released by r.Close() before OpenFile returns, matching the lifetime of the previous *os.File.

Changes:

  • excelize.go: use mmap.Open and *mmap.ReaderAt in OpenFile; close the mapping on success and on error from openReaderAt.
  • go.mod / go.sum: add golang.org/x/exp for the mmap subpackage.

Related Issue

Closes #2121Use mmap if possible (can save memory with big files).

Motivation and Context

OpenFile is the dominant entry point for reading spreadsheets from disk. Even after recent work to read the unencrypted workbook lazily through io.ReaderAt, every section access still went through a buffered read into a Go heap buffer, so opening a large .xlsx produced RSS growth and GC churn proportional to the file size touched by archive/zip.

Memory mapping avoids both costs: the kernel page cache backs the mapping, reads are zero-copy, pages are reclaimable under memory pressure, and unrelated processes opening the same file share physical memory. The behaviour requested in #2121 is achieved without changing any caller code.

How Has This Been Tested

  • go build ./... — clean.
  • go test -run 'TestOpenFile|TestOpenReader' -count=1 ./... — pass.
  • go test -count=1 ./... — full suite passes (ok github.com/xuri/excelize/v2 37.000s).

No new tests are added because the change is a transparent substitution behind an existing io.ReaderAt: the entire existing OpenFile test coverage exercises the mmap-backed path unchanged.

Types of changes

  • Docs change / refactoring / dependency upgrade
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

Replace os.Open with golang.org/x/exp/mmap.Open in OpenFile so the
operating system page-maps the .xlsx into the process address space.
Subsequent zip section reads served by archive/zip are zero-copy from
the page cache instead of being read through buffered syscalls into
Go heap memory, reducing RSS growth and GC pressure when opening
large spreadsheet files.
@artur-chopikian artur-chopikian marked this pull request as ready for review May 18, 2026 14:12
@codecov
Copy link
Copy Markdown

codecov Bot commented May 18, 2026

Codecov Report

❌ Patch coverage is 75.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 99.60%. Comparing base (6bfbbee) to head (a225fe9).

Files with missing lines Patch % Lines
excelize.go 75.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2331      +/-   ##
==========================================
- Coverage   99.60%   99.60%   -0.01%     
==========================================
  Files          32       32              
  Lines       26794    26793       -1     
==========================================
- Hits        26688    26687       -1     
  Misses         55       55              
  Partials       51       51              
Flag Coverage Δ
unittests 99.60% <75.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.

@xuri xuri added the size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. label May 19, 2026
Copy link
Copy Markdown
Member

@xuri xuri left a comment

Choose a reason for hiding this comment

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

Thanks for your PR. Any benchmark data on the performance impact of using golang.org/x/exp/mmap instead of os? Specifically, how much memory is saved, and any speed impact? I suggest test with excelize-benchmark for performance related changes.

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

Labels

size/XS Denotes a PR that changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use mmap if possible (can save memory with big files)

2 participants