Skip to content

perf: map Array.mapFinIdx/mapIdx in place#14224

Merged
kim-em merged 1 commit into
masterfrom
array-mapfinidx-inplace
Jul 4, 2026
Merged

perf: map Array.mapFinIdx/mapIdx in place#14224
kim-em merged 1 commit into
masterfrom
array-mapfinidx-inplace

Conversation

@kim-em

@kim-em kim-em commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

This PR gives Array.mapFinIdxM an @[implemented_by] unsafe implementation so that mapFinIdx, mapIdxM, and mapIdx map in place, like Array.map.

Array.map (via mapMUnsafe) nulls each slot before calling f, so on a uniquely-owned input every element arrives exclusive and f can mutate it without copying. mapFinIdxM had no such implementation — only its push-based reference body, which reads as[i] as a borrow, leaving each element shared with the still-live input array.

This is a silent performance footgun: reaching for mapFinIdx/mapIdx instead of map just to get the index turns in-place work into copying. For an Array (Array _), a column-style operation that bumps one entry of every row becomes O(rows · cols) instead of O(rows) — each row is fully copied to change a single entry — and the gap widens without bound as the rows get wider (measured 6.5× → 17× → 21× as row width goes 32 → 256 → 2048).

The fix mirrors mapMUnsafe exactly, threading i.toNat and supplying the in-bounds proof via lcProof. The reference body of mapFinIdxM is unchanged, so its lemmas still hold; @[inline] is dropped (and @[expose] kept) because inlining the reference body would bypass @[implemented_by], exactly as mapM is not @[inline].

tests/compile/arrayMapIdxInPlace.lean is a deterministic regression test (no timing) using dbgTraceIfShared: before this change the mapFinIdx/mapIdx cases each printed one row shared trace per row; now they print none, matching Array.map.

Draft: validated against a faithful standalone reproduction on v4.32.0-rc1 (stock prints one trace per row, patched prints none, checksums match); the full-stage build and test suite are left to CI.

🤖 Prepared with Claude Code

`Array.mapFinIdxM` (and hence `mapFinIdx`, `mapIdxM`, `mapIdx`) ran only its
push-based reference implementation: it reads each element with `as[i]`, a
borrow that leaves the element shared with the still-live input array. Any
in-place work the mapping function attempts on an element therefore copies it,
so a column-style operation that touches one entry of each row of an
`Array (Array _)` is O(rows * cols) instead of O(rows), and the gap widens
without bound as the rows get wider.

Give `mapFinIdxM` an `@[implemented_by]` unsafe implementation mirroring
`mapMUnsafe`: it nulls each slot (`uset i default`) before invoking `f`, so on a
uniquely-owned input each element arrives exclusive (refcount 1) and may be
mutated in place. The reference body is unchanged, so every lemma about
`mapFinIdxM` still holds; the `@[inline]` is dropped (kept `@[expose]`) because
inlining the reference body at call sites would bypass `@[implemented_by]`, just
as `mapM` is not `@[inline]`.

tests/compile/arrayMapIdxInPlace.lean is a deterministic regression test using
`dbgTraceIfShared`: before this change the `mapFinIdx`/`mapIdx` cases each
printed one "row shared" trace per row; now they print none, matching
`Array.map`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kim-em kim-em marked this pull request as ready for review June 30, 2026 13:21
@github-actions github-actions Bot added the toolchain-available A toolchain is available for this PR, at leanprover/lean4-pr-releases:pr-release-NNNN label Jun 30, 2026
@mathlib-lean-pr-testing

Copy link
Copy Markdown

Mathlib CI status (docs):

  • ❗ Mathlib CI can not be attempted yet, as the nightly-testing-2026-06-30 tag does not exist there yet. We will retry when you push more commits. If you rebase your branch onto nightly-with-mathlib, Mathlib CI should run now. You can force Mathlib CI using the force-mathlib-ci label. (2026-06-30 13:29:02)

@leanprover-bot leanprover-bot added the builds-manual CI has verified that the Lean Language Reference builds against this PR label Jun 30, 2026
@leanprover-bot

leanprover-bot commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Reference manual CI status:

@kim-em kim-em added this pull request to the merge queue Jul 4, 2026
Merged via the queue into master with commit 0680117 Jul 4, 2026
27 checks passed
Comment on lines +790 to +791
-- This must not be `@[inline]`: inlining the reference body at call sites would
-- bypass `@[implemented_by]` and lose the in-place behaviour (cf. `mapM`).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Surely this goes without saying?

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

Labels

builds-manual CI has verified that the Lean Language Reference builds against this PR toolchain-available A toolchain is available for this PR, at leanprover/lean4-pr-releases:pr-release-NNNN

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants