Skip to content

Share the empty-array singleton for zero-length Array results - #20388

Draft
T-Gro wants to merge 4 commits into
mainfrom
t-gro-array-collect-empty-perf
Draft

Share the empty-array singleton for zero-length Array results#20388
T-Gro wants to merge 4 commits into
mainfrom
t-gro-array-collect-empty-perf

Conversation

@T-Gro

@T-Gro T-Gro commented Aug 27, 2026

Copy link
Copy Markdown
Member

Fixes #20382

Array.collect/Array.map and their kin allocated a fresh zero-length array for empty results — Array.collect on empty input allocated two (the intermediate 'U[][] and the concat output). Now the internal allocation primitive zeroCreateUnchecked hands back the shared System.Array.Empty<_>() singleton when the length is 0, so empty results across Array and Array.Parallel — and even zeroCreate/create/init at length 0 — allocate nothing.

Empty-sharing is the default: the handful of sites where the length is provably > 0 opt out via zeroCreateUncheckedNonEmpty to skip the branch. Non-empty results are unaffected.

Add a central Basics.Array.zeroCreateUncheckedOrEmpty primitive that returns the
shared System.Array.Empty<_>() singleton (via the [||] literal) when count = 0, and
route the reachable-zero allocation sites through it: subUnchecked (all slicing),
concatArrays (concat + collect output), collect/map/mapi/indexed and their
Array.Parallel counterparts. Existing 'if length = 0' guards now return [||] too.

This removes the redundant zero-length array allocations reported in Array.collect
(intermediate 'U[][] plus concat output) and Array.map with empty inputs, without
adding per-function length checks (the single check lives in the primitive; fill
loops naturally no-op at length 0).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

❗ Release notes required

You can open this PR in browser to add release notes: open in github.dev


✅ Found changes and release notes in following paths:

Change path Release notes path Description
`src/FSharp.Core` docs/release-notes/.FSharp.Core/11.0.100.md

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added the AI-Tooling-Check-Bypassed Tooling check: non-fork PR, not diff-analyzed label Aug 27, 2026
Route the remaining reachable-zero allocation sites through
zeroCreateUncheckedOrEmpty: append, distinct, map2/mapi2/map3, partition,
partitionWith (+ scatterPartitioned), zip/zip3, allPairs, unzip/unzip3, rev,
removeAt/removeManyAt, transpose, and the Array.Parallel choose/mapi/zip/filter/
partition/partitionWith/groupBy builders. Count-based constructors (zeroCreate/
create/init/replicate/random*), the sort helpers and provably-nonzero sites keep
the branch-free zeroCreateUnchecked.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@T-Gro
T-Gro marked this pull request as draft August 27, 2026 22:00
Invert the primitive: zeroCreateUnchecked now shares the empty-array singleton
when count = 0 (the improve-by-default behavior), and a new
zeroCreateUncheckedNonEmpty keeps the branch-free newarr for the ~18 sites where
the length is provably > 0 (countBy/groupBy/choose chunks, filter, windowed,
chunkBySize, insert/update, reduceBy, scanSubRight, splitInto, ...).

This shrinks the diff and makes every reachable-zero site - including the
count-based constructors (zeroCreate/create/init/replicate) - share the singleton
for free, instead of each caller opting in.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@T-Gro
T-Gro force-pushed the t-gro-array-collect-empty-perf branch from 5ce67ee to a0cb3f5 Compare August 27, 2026 23:16
@Lanayx

Lanayx commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Shouldn't the same optimization be done for empty list as well?

@Numpsy

Numpsy commented Aug 28, 2026

Copy link
Copy Markdown

I think there's a few other places that could possibly do the same. e.g. Seq.toArray appears to be already special casing an empty array in the general fallback path, but not in the ICollection special case

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

Labels

AI-Tooling-Check-Bypassed Tooling check: non-fork PR, not diff-analyzed

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

Performance of Array.collect with empty inputs

3 participants