Skip to content

feat: Ordered gap buffer + Fenwick tree, benchmarks, fix iterator deadlock#29

Merged
freeformz merged 4 commits intomainfrom
worktree-check
Mar 7, 2026
Merged

feat: Ordered gap buffer + Fenwick tree, benchmarks, fix iterator deadlock#29
freeformz merged 4 commits intomainfrom
worktree-check

Conversation

@freeformz
Copy link
Copy Markdown
Owner

@freeformz freeformz commented Mar 7, 2026

Summary

Ordered rewrite: gap buffer + Fenwick tree

  • Remove improved from O(N) to O(log N) amortized using a gap buffer with lazy compaction
  • At and Index are now O(log N) via Fenwick tree (binary indexed tree) queries
  • Add and Contains remain O(1) amortized and O(1) respectively
  • Auto-compaction when gaps exceed 2x live elements

Comprehensive benchmarks

  • Standard Go benchmarks (bench_test.go) and statistical benchmarks (bench_stats_test.go)
  • Covers Add, Remove, Contains, Clone, Filter, MapBy, Chunk, Union, Intersection, Difference, SymmetricDifference
  • All 5 implementations (Map, SyncMap, Locked, Ordered, LockedOrdered), both int and string types, sizes 10 to 1M (10M behind BENCH_LARGE=1)
  • Statistical benchmarks output CSV with min/max/avg/stddev/p50/p95/p99
  • Python chart generator (benchmarks/bench_plot.py) with pre-generated charts in benchmarks/bench_charts/

Fix iterator deadlock in Locked/LockedOrdered

  • Iterator, Ordered, and Backwards methods previously held a read lock for the entire duration of iteration
  • Calling any mutating method (Add, Remove, Clear, Pop) on the same set from within the yield callback caused a deadlock (sync.RWMutex is not reentrant)
  • Now uses snapshot iteration: elements are collected under a read lock, lock is released, then the snapshot is iterated
  • Tradeoff: O(n) allocation per iteration call, but eliminates the deadlock entirely

README updates

  • Added Benchmarks section with instructions for running benchmarks
  • Embedded all 11 benchmark charts organized by category (per-element, batch, two-set operations)

Test plan

  • All existing tests pass with -race on macOS
  • No gopls diagnostics
  • Standard benchmarks run successfully
  • Statistical benchmarks produce valid CSV output
  • Snapshot iteration verified safe under concurrent access via existing testSetConcurrency tests

🤖 Generated with Claude Code

freeformz and others added 2 commits March 6, 2026 16:27
Ordered.Remove improves from O(N) to O(log N) amortized using a gap
buffer with a Fenwick tree (binary indexed tree) for rank queries.
At(i) and Index(m) change from O(1) to O(log N). Auto-compacts when
gaps exceed 2x alive elements.

Add comprehensive benchmarks:
- bench_test.go: standard Go benchmarks for all 5 implementations
- bench_stats_test.go: statistical benchmarks (BENCH_STATS=1) outputting
  CSV with min/max/avg/stddev/percentiles
- bench_plot.py: generates per-operation PNG charts from CSV
- bench_stats.csv: baseline results (660 records)
- bench_charts/: 11 PNG charts

Also fix zero-value style: use `var x T` instead of `x := 0`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Iterator, Ordered, and Backwards methods on Locked and LockedOrdered
previously held a read lock for the entire duration of iteration. This
caused a deadlock when the yield callback called any mutating method
(Add, Remove, Clear, Pop) on the same set, since sync.RWMutex is not
reentrant.

Now these methods snapshot the elements under a read lock, release the
lock, then iterate the snapshot. This eliminates the deadlock while
preserving point-in-time consistency. The tradeoff is an O(n) allocation
per iteration call.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 7, 2026 01:19
Move bench_plot.py, bench_stats.csv, and bench_charts/ into a
benchmarks/ subdirectory. Add a Benchmarks section to the README
with instructions for running benchmarks and all 11 charts showing
performance across implementations and set sizes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR rewrites the Ordered set implementation with a gap buffer + Fenwick tree (binary indexed tree) data structure to improve Remove from O(N) to O(log N), while keeping Add at O(1) amortized and Contains at O(1). It also fixes a deadlock in Locked/LockedOrdered iterator methods by switching to snapshot-based iteration, and adds comprehensive benchmarks.

Changes:

  • Rewrite Ordered internals: replace values []M with slots []M, alive []bool, bit []int (Fenwick tree), and count int. Add auto-compaction when gaps exceed 2× live elements.
  • Fix iterator deadlock in Locked/LockedOrdered: Iterator, Ordered, and Backwards methods now collect a snapshot under read lock, then iterate without holding the lock.
  • Add comprehensive benchmark infrastructure: standard Go benchmarks (bench_test.go), statistical benchmarks with CSV output (bench_stats_test.go), and a Python visualization script (bench_plot.py).

Reviewed changes

Copilot reviewed 9 out of 20 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
ordered.go Rewrite internals with gap buffer + Fenwick tree; update all methods (Add, Remove, At, Index, Sort, Clear, Iterator, etc.)
locked.go Switch Iterator to snapshot-based iteration to prevent deadlock
locked_ordered.go Switch Iterator, Ordered, Backwards to snapshot-based iteration to prevent deadlock
set.go Minor style change: i := 0var i int in Random function
bench_test.go New standard Go benchmarks for all set implementations and operations
bench_stats_test.go New statistical benchmark framework with CSV output, auto-calibration, and percentile computation
bench_stats.csv Pre-generated benchmark results (661 lines)
bench_plot.py Python script to generate per-operation charts from CSV data

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Add TestOrdered_AtIndex_AfterRemoval: targeted test exercising At()
  and Index() after removals, verifying Fenwick tree consistency both
  before and after compaction.
- Fix writeStatsCSV: check csv.Writer.Error() after Flush and propagate
  f.Close() errors instead of silently swallowing write failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 21 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@freeformz freeformz merged commit 1c028a9 into main Mar 7, 2026
13 checks passed
@freeformz freeformz deleted the worktree-check branch March 7, 2026 02:18
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