Optimize TypedArray bulk operations with Span/Array.Copy#2488
Merged
Conversation
Replace per-byte/per-element loops in TypedArray methods with bulk memory operations, addressing a review finding that the byte-by-byte copy loops in set/slice were the real bottleneck (not the per-iteration constraint counters). - set/slice/copyWithin/with: same-type transfers use System.Array.Copy (memmove); set skips the now-redundant clone for the same-type path - fill: materialize the value once, replicate via exponential doubling - reverse/toReversed: vectorized Span<T>.Reverse over the backing buffer - indexOf/lastIndexOf/includes: vectorized Span<T>.IndexOf/LastIndexOf for integer element types (exactly-representable search values, little-endian) - constructor from a CLR array: bulk MemoryMarshal copy instead of per-element Convert.ToDouble boxing Also closes a pre-existing spec gap: set/copyWithin on a view over an immutable ArrayBuffer now throw TypeError (the direct SetValueInBuffer path bypassed AssertNotImmutable). slice keeps a forward byte-by-byte copy when a @@species target aliases the source buffer (spec-mandated overlap "smear"); the search fast paths fall back to the generic per-element loop when fromIndex/searchElement coercion detaches or shrinks the buffer. 99,015 test262 pass, 0 regressions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Owner
|
Can you do the same on my bank account? |
Collaborator
Author
Nice example of spec vs optimal. |
This was referenced Jun 4, 2026
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.
Summary
TypedArrays are commonly used in high-performance scenarios, but several
%TypedArray%.prototypemethods copied/scanned the backing buffer one byte (or one element) at a time, routing each step throughGetValueFromBuffer/SetValueInBuffer(aTypedArrayValuedecode/encode plus anArray.Copy(elementSize)). This started from a review finding that the per-iteration constraint counters in theset/slicebyte-copy loops were correct but masked the real win: replacing the byte-by-byte copy with a single bulkSystem.Array.Copy/Spanoperation, which also moves the constraint check out of the hot loop.The backing store is a plain
byte[](JsArrayBuffer._arrayBufferData), soSystem.Array.Copy— already the established bulk-copy primitive in this codebase (CloneArrayBuffer,Resize,ArrayBufferPrototype.Slice) and documented to handle overlapping regions with memmove semantics — applies directly.Changes
set/slice/copyWithin/with— same-element-type transfers now use a singleSystem.Array.Copy.setalso skips the now-redundant same-buffer clone for the same-type path (memmove gives the bit-identical result without the allocation).fill— materialize the fill value once, then replicate its byte pattern across the range via exponential doubling (endian-agnostic, all TFMs).reverse/toReversed— reverse element order directly in the buffer using the vectorizedSpan<T>.Reverse(size-dispatched; endian-agnostic since whole element blocks are swapped by position).indexOf/lastIndexOf/includes— for integer element types, when the search value is exactly representable and the platform is little-endian, scan with the vectorizedSpan<T>.IndexOf/LastIndexOf. All other cases fall back to the existing element loop.FillTypedArrayInstance<T>) — bulkMemoryMarshalcopy instead of a per-elementConvert.ToDouble(which boxed every element).Correctness notes
set/copyWithinon a view over an immutableArrayBuffernow throwTypeError. The directSetValueInBufferbyte path bypassedAssertNotImmutable(only theo[i] = valueindexer enforced it).slicealiasing: when a@@speciesconstructor returns a target that aliases the source buffer,slicekeeps a forward byte-by-byte copy (the spec-mandated overlap "smear"), since memmove would differ there.fromIndex/searchElementcoercion detached or shrank the buffer.Verification
@@species-aliasing, and resizable-buffer-during-coercion cases.Jint.Testssuite green on net10.0 and net472.net462;netstandard2.0;netstandard2.1;net8.0;net10.0.Benchmarks
New
TypedArrayBulkBenchmark(10,000-element arrays, BenchmarkDotNet ShortRun, AMD Ryzen 9 5950X, .NET 10). Before = base of this branch, after = this PR, same benchmark in both runs:setset(self-overlapping)slicecopyWithinfillreversetoReversedwithindexOf(miss)includes(miss)🤖 Generated with Claude Code