Skip to content

perf: optimize merge overlap check with coordinate cache#46

Merged
Nebu1eto merged 2 commits into
mainfrom
perf/phase4-merge-overlap
Feb 11, 2026
Merged

perf: optimize merge overlap check with coordinate cache#46
Nebu1eto merged 2 commits into
mainfrom
perf/phase4-merge-overlap

Conversation

@Nebu1eto

Copy link
Copy Markdown
Owner

Summary

  • Eliminate O(n^2) string parsing in merge overlap detection by caching parsed coordinates
  • Add cached_coords: Vec<(u32, u32, u32, u32)> field to MergeCells with #[serde(skip)] for zero serialization impact
  • Lazy initialization on first access, incremental maintenance on add/remove, automatic invalidation on row/col shift operations

Performance Impact

  • Before: Each overlap check parsed existing merge reference strings (2x cell_name_to_coordinates per region). For 500 sequential inserts, ~125,000 string-parsing operations.
  • After: Each overlap check is a 4-integer comparison. String parsing happens only once per region (on first access). Memory overhead: 16 bytes per merge region.
  • Target: Write 500 merged regions >= 40% improvement (from 8ms toward <= 4ms)

Changes

  • crates/sheetkit-xml/src/worksheet.rs: Add cached_coords field to MergeCells
  • crates/sheetkit-core/src/merge.rs: ensure_cache() helper + cached overlap check
  • crates/sheetkit-core/src/stream.rs: Initialize cache in StreamWriter
  • crates/sheetkit-core/src/workbook/sheet_ops.rs: Invalidate cache on row/col shifts

Test plan

  • 5 new tests (cache sync, lazy init, 500 regions, unmerge+add, no-serialize)
  • All 1,961 existing tests pass
  • cargo clippy --workspace clean
  • cargo fmt --check pass
  • Round-trip fidelity preserved (serde skip)

🤖 Generated with Claude Code

The merge overlap check was O(n^2) with string parsing on every comparison:
for each new merge region, all existing references were re-parsed from their
string form (e.g. "A1:C3") into numeric coordinates. This caused measurable
slowdown when adding 500+ merge regions.

Add a `cached_coords` field (serde-skipped) to `MergeCells` that stores
pre-parsed `(min_col, min_row, max_col, max_row)` tuples in parallel with
the reference strings. The cache is lazily populated on first overlap check
(handling worksheets deserialized from XML) and maintained incrementally on
add/remove. Reference string shifting (row/col insert) invalidates the cache
so it rebuilds on next use.

This eliminates all string parsing from the hot path: overlap checks become
pure numeric comparisons at 16 bytes per region, with negligible memory
overhead for the common case (< 50 merge regions).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Nebu1eto

Copy link
Copy Markdown
Owner Author

리뷰 코멘트 공유드립니다.

  • [P2] MergeCells에 추가된 cached_coords는 transient cache인데, 현재 MergeCells#[derive(PartialEq)]라서 캐시 채움 여부만으로 == 결과가 달라질 수 있습니다.
    • 참고: crates/sheetkit-xml/src/worksheet.rs:691, crates/sheetkit-xml/src/worksheet.rs:703
    • 영향: XML 의미가 같은 객체라도 연산 순서(merge 체크 수행 여부)에 따라 equality가 달라질 수 있습니다.

참고로 로컬에서 PR 브랜치 기준 cargo test -p sheetkit-core merge는 통과 확인했습니다.

The cached_coords field is a runtime performance cache that is not
serialized. With the derived PartialEq, two MergeCells with identical
XML content could compare as unequal depending on whether the cache
had been populated. Implement PartialEq manually to compare only
count and merge_cells, making equality independent of cache state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude

claude Bot commented Feb 11, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@Nebu1eto
Nebu1eto merged commit 251ba67 into main Feb 11, 2026
7 checks passed
@Nebu1eto
Nebu1eto deleted the perf/phase4-merge-overlap branch February 11, 2026 15:17
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