Skip to content

perf(immut): bulk build hash collections#3746

Open
mizchi wants to merge 1 commit into
moonbitlang:mainfrom
mizchi:perf/hamt-build
Open

perf(immut): bulk build hash collections#3746
mizchi wants to merge 1 commit into
moonbitlang:mainfrom
mizchi:perf/hamt-build

Conversation

@mizchi

@mizchi mizchi commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

What changed

  • Add a bulk construction path for immutable HashMap::from_array / from_iter and HashSet::from_array / from_iter.
  • The builder partitions entries by HAMT path segment and constructs branches from contiguous ranges, avoiding repeated persistent add work for larger inputs.
  • Avoid the hot SparseArray::add path during bulk construction by adding @immut/internal/sparse_array.from_sorted_fixed_array and building branch children in one allocation.
  • Preallocate BuildEntry arrays for from_array, and use iterator size_hint as capacity for from_iter.
  • Keep the old add loop for small inputs (<= 64) to avoid bulk-builder overhead.
  • Preserve existing duplicate semantics:
    • HashMap::from_array remains first value wins.
    • HashMap::from_iter remains last value wins.
    • HashSet::from_array / from_iter keep their existing duplicate representative behavior.
  • Add build benchmarks for HashMap / HashSet.
  • Add missing docs for public bench clock helpers and JS BigInt APIs that were surfaced by moon check.

Profile findings

moon test --profile requires xcrun xctrace; on this machine I ran it with:

DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer moon test -p immut/hashmap --target native --profile --filter "profile HashMap*"
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer moon test -p immut/hashset --target native --profile --filter "profile HashSet*"

The temporary profile drivers repeatedly built a 10k-element collection and were not committed.

Before the SparseArray change, the profile showed SparseArray::add as a real hotspot:

  • HashMap: SparseArray::add was 17.5% inclusive, mostly moonbit_make_ref_array_with_blit / moonbit_unsafe_ref_array_blit.
  • HashSet: SparseArray::add was 18.1% inclusive with the same copy/blit cost.

After constructing sparse arrays from sorted child indices, SparseArray::add disappeared from the top profile entries. Samples for the same profile driver dropped:

  • HashMap: 1768 -> 1162 samples
  • HashSet: 1799 -> 1171 samples

The remaining top cost is now build_*_node_range plus allocation/drop overhead while partitioning each HAMT level.

Benchmarks

moon bench -p immut/hashmap --target native

benchmark before after
HashMap::from_array ordered n=10000 1.59 ms 608.22 us
HashMap::from_array random n=10000 1.59 ms 609.36 us
HashMap::from_iter random n=10000 1.61 ms 624.88 us

moon bench -p immut/hashset --target native

benchmark before after
HashSet::from_array ordered n=10000 1.57 ms 549.90 us
HashSet::from_array random n=10000 1.57 ms 551.03 us
HashSet::from_iter random n=10000 1.58 ms 562.71 us

Validation

  • moon fmt
  • moon info
  • moon check -p bench --target all
  • moon check -p bigint --target all
  • moon check -p immut/internal/sparse_array --target all
  • moon check -p immut/hashmap --target all
  • moon check -p immut/hashset --target all
  • moon test -p bench --target all
  • moon test -p bigint --target all
  • moon test -p immut/internal/sparse_array --target all
  • moon test -p immut/hashmap --target all
  • moon test -p immut/hashset --target all
  • moon bench -p immut/hashmap --target native
  • moon bench -p immut/hashset --target native
  • git diff --check

The missing-doc warnings seen earlier are fixed.

@mizchi
mizchi force-pushed the perf/hamt-build branch from 742ec6f to c174e97 Compare June 29, 2026 08:21
@mizchi
mizchi marked this pull request as ready for review June 29, 2026 11:17
Copilot AI review requested due to automatic review settings June 29, 2026 11:17
@mizchi
mizchi force-pushed the perf/hamt-build branch from c174e97 to 542fead Compare June 29, 2026 11:21
@mizchi
mizchi marked this pull request as draft June 29, 2026 11:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 improves construction performance for immutable HAMT-backed HashMap and HashSet by introducing a bulk-build path for from_array / from_iter, avoiding repeated persistent add work and reducing allocations during branch construction.

Changes:

  • Added a bulk HAMT builder that partitions entries by path segment and builds branch nodes from contiguous ranges, with a small-input fast path (≤ 64) that keeps the existing add loop.
  • Introduced @immut/internal/sparse_array.from_sorted_fixed_array to build SparseArray children in a single allocation during bulk construction.
  • Added build benchmarks and extended tests to ensure duplicate-handling semantics remain unchanged.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.

Show a summary per file
File Description
immut/internal/sparse_array/sparse_array.mbt Adds from_sorted_fixed_array to support single-allocation sparse-array construction for bulk HAMT builds.
immut/internal/sparse_array/sparse_array_test.mbt Adds coverage for SparseArray::from_sorted_fixed_array.
immut/internal/sparse_array/pkg.generated.mbti Exposes the new sparse-array constructor in the generated interface.
immut/hashset/moon.pkg Imports bench for test/bench harness integration.
immut/hashset/hashset_build_bench_test.mbt Adds benchmarks for HashSet::from_array / from_iter bulk construction performance.
immut/hashset/HAMT.mbt Implements bulk-build construction for HashSet::from_array / from_iter using segmented partitioning and sparse-array bulk child creation.
immut/hashset/HAMT_test.mbt Adds tests to verify bulk-build matches prior add-based behavior, including duplicate representative semantics.
immut/hashmap/moon.pkg Imports bench for test/bench harness integration.
immut/hashmap/hashmap_build_bench_test.mbt Adds benchmarks for HashMap::from_array / from_iter bulk construction performance.
immut/hashmap/HAMT.mbt Implements bulk-build construction for HashMap::from_array / from_iter, preserving first/last duplicate semantics as before.
immut/hashmap/HAMT_test.mbt Adds tests to verify bulk-build matches prior add-based behavior, including duplicate value semantics.

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

@mizchi
mizchi marked this pull request as ready for review June 29, 2026 11:23
@mizchi
mizchi force-pushed the perf/hamt-build branch 4 times, most recently from af17be3 to fb44460 Compare July 5, 2026 15:47
@mizchi
mizchi force-pushed the perf/hamt-build branch from fb44460 to bf4d7a5 Compare July 7, 2026 12:35
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