Repository navigation
perf(virtualized-lists): drop dead stale-index filter in ViewabilityHelper - #59590
seyedmostafahasani wants to merge 3 commits into
Conversation
…ityHelper _onUpdateSync filtered the indices to check with this._viewableIndices.includes(ii), which is O(n) per index and O(n^2) per update. Build a Set of the current viewable indices once and use has() instead. Behavior is unchanged.
|
Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
|
|
|
Thanks for the review, and agreed on the numbers. In my benchmark the Set only breaks even at around 50-100 viewable indices (122 ns vs 363 ns at n=10, so ~3x slower there), and with I'll try your suggestion of building the Set once in |
|
Thanks again for the review. I tried your suggestion and also looked at the call sites more closely. Measured on the real While reading the code I noticed the filter in Instead of the Set, I'd change this PR to skip the filter when the input is the stored array: if (viewableIndicesToCheck !== this._viewableIndices) {
viewableIndicesToCheck = viewableIndicesToCheck.filter(ii =>
this._viewableIndices.includes(ii),
);
}Behavior stays identical for any other caller, and all 22 existing ViewabilityHelper tests pass. Measured speedup vs. current main: ~1.0-1.1x at 10-50 viewable items (within noise), ~1.2x at ~200, ~1.6x at ~800. So the win only matters for very large viewable sets. If you'd prefer removing the dead filter entirely, or think this isn't worth changing, I'm happy to do that or close the PR instead. |
|
Please make the change in the PR - would be great if you could have data on time spent in JS per scroll before/after |
…Helper._onUpdateSync Both call sites pass the array stored in this._viewableIndices, so the filter always keeps every index. Skip it when the input is the stored array and keep the filter for any other caller. Drops the Set; behavior is unchanged.
|
Updated the PR as discussed: JS time in Setup: real
Run-to-run ranges overlap (e.g. 800 items: 1,324–1,555 ms main vs 1,137–1,425 ms guard per 300 updates), so only the 800-item gain is plausibly real. At typical sizes (~10 viewable items) the change is neutral, so it's your call whether it's worth landing. |
javache
left a comment
There was a problem hiding this comment.
There's already an early bail-out here: https://github.com/react/react-native/blob/main/packages/virtualized-lists/Lists/ViewabilityHelper.js#L236
_onUpdateSync is called after assigning it here https://github.com/react/react-native/blob/main/packages/virtualized-lists/Lists/ViewabilityHelper.js#L245C5-L245C44 - so this filter will always skip - can we just drop it completely?
…elper _onUpdateSync is only called right after this._viewableIndices is assigned (or from a timer guarded by _updateGeneration), so the filter always keeps every index. Remove it, per review feedback.
|
@javache has imported this pull request. If you are a Meta employee, you can view this in D128673216. |
|
Can you update the PR title and description to reflect the change made? |
|
@javache I've updated the PR title and description to reflect the final change (dropping the dead stale-index filter). Could you take another look? |
Summary:
ViewabilityHelper._onUpdateSyncfilteredviewableIndicesToCheckwiththis._viewableIndices.includes(ii), which is O(n) per index and therefore O(n^2) per update. The filter is dead code:onUpdateassigns the array tothis._viewableIndicesright before calling_onUpdateSync, and theminimumViewTimetimer already returns early when_updateGenerationhas changed (every reassignment of_viewableIndicesbumps it). Both call sites therefore pass the very same array that is stored in_viewableIndices, so the filter always keeps every index.This removes the filter and uses
viewableIndicesToCheckdirectly. The PR is a 4-line deletion with no behavior change.Changelog:
[INTERNAL] [REMOVED] - Remove no-op stale-index filter in ViewabilityHelper
Test Plan:
No behavior change. The existing
ViewabilityHelper-test.jscases cover this path, in particular "minimumViewTime ignores stale viewability updates". Run withyarn jest packages/virtualized-lists/Lists.Performance:
The main benefit is removing dead code. Measured on the real
ViewabilityHelper.onUpdate(Node v22 + Jest, 20k-item list, scrolling one item per tick so the viewable set changes on every update, 300 updates per run, median of 7 interleaved runs) with an earlier iteration of this PR that skipped the filter via an identity check. That variant does the same work as removing the filter for these call sites.At typical sizes (~10 viewable items) the change is neutral. Run-to-run ranges overlap, so only the 800-item gain is plausibly real.