Skip to content

[VM] Speed up TypedArray creation from arrays - #2219

Open
goutamadwant wants to merge 3 commits into
meta:static_hfrom
goutamadwant:fix/typed-array-from-fast-path-2215
Open

goutamadwant wants to merge 3 commits into
meta:static_hfrom
goutamadwant:fix/typed-array-from-fast-path-2215

Conversation

@goutamadwant

@goutamadwant goutamadwant commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Add a guarded fast path for creating typed arrays from plain JSArray values when both the array iterator and its next method are unchanged.

For arrays that pass arrayFastPathCheck(), copy the indexed storage and normalize empty slots to undefined. Other guarded arrays use an indexed loop that preserves dynamic length and indexed getters. Inputs with customized iterator behavior continue through the generic iterable path.

Add regression coverage for array growth and shrinkage, conversion and mapping side effects, customized iterator methods, and a deleted interior element. The deleted-element case checks floating-point and integer conversion, mapper input, and source preservation.

Fixes #2215

Test Plan

  • ./utils/format.sh
  • Targeted test/hermes/TypedArray.js LIT test: passed under both Hermes and Static Hermes.
  • Verified that the new deleted-element test exercises the empty-slot normalization branch using a temporary local trace, removed before the final build.
  • LIT_OPTS=-j4 cmake --build cmake-build-release --target check-hermes -j 8
    • 4,094 expected passes
    • 6 expected failures
    • 186 unsupported tests
    • No unexpected failures

Benchmark

Reran the script from #2215 with matched Release builds of the baseline (ade4a2b20) and this branch. Both used -O3 -DNDEBUG with -gline-tables-only and the same compiler.

Each run uses 10 warmups, 30 samples, a 200-element source array, and 20,000 copies per sample. Ran baseline then updated, followed by updated then baseline. Ranges below span the two run medians, in milliseconds:

  • Float64Array.from(array): baseline 518-755; updated 40-48.
  • new Float64Array(array): baseline 505-557; updated 39 in both runs.
  • Uint8Array.from(array): baseline 612-644; updated 43-44.
  • Indexed fill loop: baseline 113-126; updated 110-129.

Avoid iterator and iterator-result allocations when a JSArray uses the built-in array iterator methods. Preserve dynamic length, indexed access, and snapshot semantics while falling back for customized iterators.

Add regression coverage for array growth and shrinkage, conversion and mapping side effects, and customized iterator methods.

Fixes meta#2215
@meta-cla meta-cla Bot added the CLA Signed Do not delete this pull request or issue due to inactivity. label Oct 3, 2026

@lavenzg lavenzg 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.

Overall looks good.

// Avoid allocating iterator and iterator-result objects for ordinary arrays.
// Both methods must be unmodified because the generic iterator protocol
// observes each of them.
auto sourceArray = Handle<JSArray>::dyn_vmcast(items);

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.

Can you put this in a new method, iterableToArrayLikeFastPath?

Comment thread lib/VM/JSLib/TypedArray.cpp Outdated
while (k < JSArray::getLength(*lv.sourceArray, runtime)) {
GCScopeMarkerRAII marker{gcScope};

lv.nextValue = lv.sourceArray->at(runtime, k).unboxToHV(runtime);

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.

I think ideally we want to to have another fast path for arrays that has no accessors and indexed properties in bound (see arrayFastPathCheck()).

@lavenzg lavenzg 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.

Could you update the benchmark numbers?

JSArray::putLengthUnsafe(lv.A.get(), runtime, newLength);
for (uint32_t k = 0; k < length; ++k) {
if (LLVM_UNLIKELY(destStorage->at(k).isEmpty())) {
destStorage->set(

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.

Please add a test covering this path.

Comment thread lib/VM/JSLib/TypedArray.cpp Outdated
lv.nextValue = std::move(*valueRes);
}

if (LLVM_UNLIKELY(k == UINT32_MAX)) {

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.

This is always false since k < length and length is uint32_t.

Cover deleted array elements in constructor and from conversions, including mapper input and source preservation.

Remove an unreachable overflow check from the array-only loop, whose bound is a uint32_t array length.

Follow up on the review of meta#2219 for meta#2215.
@goutamadwant

Copy link
Copy Markdown
Contributor Author

addressed all comments @lavenzg . Added a deleted-element test covering the empty-slot copy branch, mapper receiving undefined, and source-hole preservation.

  • Removed the unreachable k == UINT32_MAX check.
  • Refreshed benchmarks using matched Release builds: Float64Array.from 40–48 ms, constructor 39 ms, and Uint8Array.from 43–44 ms. Baseline comparisons and control results are in the PR description.

Can you pls take another look and let me know if it looks good now? Thanks!

@meta-codesync

meta-codesync Bot commented Oct 22, 2026

Copy link
Copy Markdown

@lavenzg has imported this pull request. If you are a Meta employee, you can view this in D126991206.

@lavenzg

lavenzg commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

addressed all comments @lavenzg . Added a deleted-element test covering the empty-slot copy branch, mapper receiving undefined, and source-hole preservation.

Thanks! All tests still pass without the loop of setting holes to undefined. I'll amend it directly after import.

meta-codesync Bot pushed a commit that referenced this pull request Oct 29, 2026
Summary:
Add a guarded fast path for creating typed arrays from plain `JSArray` values when both the array iterator and its `next` method are unchanged.

For arrays that pass `arrayFastPathCheck()`, copy the indexed storage and normalize empty slots to `undefined`. Other guarded arrays use an indexed loop that preserves dynamic length and indexed getters. Inputs with customized iterator behavior continue through the generic iterable path.

Add regression coverage for array growth and shrinkage, conversion and mapping side effects, customized iterator methods, and a deleted interior element. The deleted-element case checks floating-point and integer conversion, mapper input, and source preservation.

Fixes #2215

X-link: #2219

Reviewed By: avp

Differential Revision: D126991206

Pulled By: lavenzg

fbshipit-source-id: e5d47db0028d2c0c43d124cc6f6553c107be0f20

This branch has not been deployed

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

Labels

CLA Signed Do not delete this pull request or issue due to inactivity.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TypedArray.from and new TypedArray(array) are ~6× slower since #1882 for plain arrays

2 participants