Repository navigation
Filter a unique list in place in List.keep_if - #12645
Merged
Merged
Conversation
braedenfoster
marked this pull request as ready for review
October 26, 2026 20:12
keep_if, drop_if, keep_oks and keep_errs folded over List.concat(acc, [item]), so keeping 12 items cost 13 allocations and 4 reallocations. keep_if now follows List.map. A unique list is compacted in place with list_swap_unsafe and truncated with List.drop_last, so it allocates nothing and keeps its capacity, as its docs promise. A shared list is copied into one List.with_capacity(len) allocation. drop_if calls keep_if. keep_oks and keep_errs change the item type, so they build their result in one List.with_capacity(len) allocation. An in-place version would need a primitive that shortens a list without releasing the slots its items were moved out of. The Builtin snapshot node ids moved with the new definitions.
A shared or static list went through one List.with_capacity(len) allocation even when keep_if kept every item or none. The shared path now tests items until the first dropped one and returns the input when there is none. Otherwise it copies the kept items into a new list that it allocates only when the first kept item needs a place, so keeping nothing allocates nothing. keep_oks and keep_errs also allocate on their first kept value. The predicate still runs once per item: the copy loop does not test the items before the first dropped one again.
braedenfoster
force-pushed
the
keep-if-in-place
branch
from
October 26, 2026 23:12
5c5039a to
cf5e609
Compare
Collaborator
|
Thanks @braedenfoster :) |
Contributor
Author
Awesome thanks 🙏 |
The shared path now allocates its List.with_capacity(len) copy up front, appends the items before the first dropped one with list_append_sublist, and tests only the items after it. This removes the per-item index check and the lazy-allocation test from the copy loop. Keeping nothing from a shared list whose first item is dropped now allocates once, which matches the documented "same memory as the original" behavior. The unique path skips list_swap_unsafe when an item is already in place. Also: document the shared-list copy in the keep_if docs, fix the seamless slice test comment, prefix the alloc-count CLI cases with the issue number, and fix a missing space in the rc_conformance test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerated the dev_object_float_math hashes, which both sides changed.
Anton-4
approved these changes
Oct 29, 2026
Collaborator
There was a problem hiding this comment.
Thanks @braedenfoster! It's nice to have these optimized 🚀
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #12644.
List.keep_if,drop_if,keep_oksandkeep_errswere a fold overList.concat(acc, [item]), so each kept item allocated a one-item list and the result grew by reallocating. This moves them onto the reuse pattern thatList.map(#9610, #10594) andsort_impluse.Changes
keep_iflist_map_prepare_reuse, thenlist_owned_unique. Each kept item is swapped down to the end of the kept prefix withlist_swap_unsafe, thenList.drop_lastreleases the dropped items. Order is kept, each dropped item is released once, capacity is unchanged, and an all-kept list comes back as it was. No allocation.list_append_unsafeinto oneList.with_capacity(len)buffer, allocated when the first kept item needs a place, so keeping nothing allocates nothing. The predicate still runs once per item.drop_ifcallskeep_ifwith the predicate negated.keep_oksandkeep_errsbuild their result in one buffer allocated on the first match. They do not work in place: whenbeforeandaftershare a layout it would be possible, but the moved-out tail slots would need a primitive that shortens a list without releasing them. That seems better as a follow-up.The in-place path uses only
list_swap_unsafeanddrop_last.listSwapcopies a shared list unless the call is marked in place, so a wrong uniqueness answer costs a copy rather than a wrong result (the #10368 failure mode).The second commit (all-kept shortcut and lazy allocation on the shared path) can be dropped or split out if you prefer a smaller change.
Allocation calls
roc_allocplusroc_reallocmade by the call alone, 12-item list,test/alloc-count, same for--opt=devand--opt=speed:keep_if, all keptkeep_if, 6 of 12 keptkeep_if, none keptdrop_if, none droppedkeep_oks, allOkTests
src/eval/test/eval_tests.zig: order kept; all, none and some kept; empty and emptied lists; a unique seamless slice; a shared list used again afterwards; a predicate that captures the list being filtered; heapStritems;drop_if,keep_oksandkeep_errs; allocation counts.src/eval/test/rc_conformance_tests.zig:keep_if,drop_ifandkeep_oksover unique and sharedStrlists.test/alloc-count/app_list_keep_if.rocwith dev and speed CLI entries.The full eval suite,
rc_conformanceand the Builtin doc tests pass locally.