Skip to content

Commit cc4c9de

Browse files
committed
[Experiment] Reuse memo cache after interruption (#28878)
Adds an experimental feature flag to the implementation of useMemoCache, the internal cache used by the React Compiler (Forget). When enabled, instead of treating the cache as copy-on-write, like we do with fibers, we share the same cache instance across all render attempts, even if the component is interrupted before it commits. If an update is interrupted, either because it suspended or because of another update, we can reuse the memoized computations from the previous attempt. We can do this because the React Compiler performs atomic writes to the memo cache, i.e. it will not record the inputs to a memoization without also recording its output. This gives us a form of "resuming" within components and hooks. This only works when updating a component that already mounted. It has no impact during initial render, because the memo cache is stored on the fiber, and since we have not implemented resuming for fibers, it's always a fresh memo cache, anyway. However, this alone is pretty useful — it happens whenever you update the UI with fresh data after a mutation/action, which is extremely common in a Suspense-driven (e.g. RSC or Relay) app. So the impact of this feature is faster data mutations/actions (when the React Compiler is used). DiffTrain build for commit ea26e38.
1 parent ec52e58 commit cc4c9de

File tree

4 files changed

+79
-10
lines changed

4 files changed

+79
-10
lines changed

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react-test-renderer/cjs/ReactTestRenderer-dev.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<f42fda0db232601ac76c5f1efaee14fd>>
10+
* @generated SignedSource<<8514bdf3885188125eab2e51d9ecb536>>
1111
*/
1212

1313
'use strict';
@@ -7505,7 +7505,30 @@ function useMemoCache(size) {
75057505

75067506
if (currentMemoCache != null) {
75077507
memoCache = {
7508-
data: currentMemoCache.data.map(function (array) {
7508+
// When enableNoCloningMemoCache is enabled, instead of treating the
7509+
// cache as copy-on-write, like we do with fibers, we share the same
7510+
// cache instance across all render attempts, even if the component
7511+
// is interrupted before it commits.
7512+
//
7513+
// If an update is interrupted, either because it suspended or
7514+
// because of another update, we can reuse the memoized computations
7515+
// from the previous attempt. We can do this because the React
7516+
// Compiler performs atomic writes to the memo cache, i.e. it will
7517+
// not record the inputs to a memoization without also recording its
7518+
// output.
7519+
//
7520+
// This gives us a form of "resuming" within components and hooks.
7521+
//
7522+
// This only works when updating a component that already mounted.
7523+
// It has no impact during initial render, because the memo cache is
7524+
// stored on the fiber, and since we have not implemented resuming
7525+
// for fibers, it's always a fresh memo cache, anyway.
7526+
//
7527+
// However, this alone is pretty useful — it happens whenever you
7528+
// update the UI with fresh data after a mutation/action, which is
7529+
// extremely common in a Suspense-driven (e.g. RSC or Relay) app.
7530+
data: // Clone the memo cache before each render (copy-on-write)
7531+
currentMemoCache.data.map(function (array) {
75097532
return array.slice();
75107533
}),
75117534
index: 0
@@ -22967,7 +22990,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition
2296722990
return root;
2296822991
}
2296922992

22970-
var ReactVersion = '19.0.0-canary-8eb36427';
22993+
var ReactVersion = '19.0.0-canary-66405eaa';
2297122994

2297222995
/*
2297322996
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0e0b69321a6fcfe8a3eaae3b1016beb110437b38
1+
ea26e38e33bffeba1ecc42688d7e8cd7e0da1c02

compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/implementations/ReactFabric-dev.fb.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<aa7d2d8bc19196a0f82f861919a366a9>>
10+
* @generated SignedSource<<1a8fdf80e78f9d6a4a8bccd8d1a9a735>>
1111
*/
1212

1313
'use strict';
@@ -10482,7 +10482,30 @@ function useMemoCache(size) {
1048210482

1048310483
if (currentMemoCache != null) {
1048410484
memoCache = {
10485-
data: currentMemoCache.data.map(function (array) {
10485+
// When enableNoCloningMemoCache is enabled, instead of treating the
10486+
// cache as copy-on-write, like we do with fibers, we share the same
10487+
// cache instance across all render attempts, even if the component
10488+
// is interrupted before it commits.
10489+
//
10490+
// If an update is interrupted, either because it suspended or
10491+
// because of another update, we can reuse the memoized computations
10492+
// from the previous attempt. We can do this because the React
10493+
// Compiler performs atomic writes to the memo cache, i.e. it will
10494+
// not record the inputs to a memoization without also recording its
10495+
// output.
10496+
//
10497+
// This gives us a form of "resuming" within components and hooks.
10498+
//
10499+
// This only works when updating a component that already mounted.
10500+
// It has no impact during initial render, because the memo cache is
10501+
// stored on the fiber, and since we have not implemented resuming
10502+
// for fibers, it's always a fresh memo cache, anyway.
10503+
//
10504+
// However, this alone is pretty useful — it happens whenever you
10505+
// update the UI with fresh data after a mutation/action, which is
10506+
// extremely common in a Suspense-driven (e.g. RSC or Relay) app.
10507+
data: // Clone the memo cache before each render (copy-on-write)
10508+
currentMemoCache.data.map(function (array) {
1048610509
return array.slice();
1048710510
}),
1048810511
index: 0
@@ -26037,7 +26060,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition
2603726060
return root;
2603826061
}
2603926062

26040-
var ReactVersion = '19.0.0-canary-bd0f09e6';
26063+
var ReactVersion = '19.0.0-canary-e42cf623';
2604126064

2604226065
/*
2604326066
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol

compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/implementations/ReactNativeRenderer-dev.fb.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<3659a427365aaec7f1c22e3be8b66f08>>
10+
* @generated SignedSource<<0ce01fc662f6de8232355aded350d529>>
1111
*/
1212

1313
'use strict';
@@ -10721,7 +10721,30 @@ function useMemoCache(size) {
1072110721

1072210722
if (currentMemoCache != null) {
1072310723
memoCache = {
10724-
data: currentMemoCache.data.map(function (array) {
10724+
// When enableNoCloningMemoCache is enabled, instead of treating the
10725+
// cache as copy-on-write, like we do with fibers, we share the same
10726+
// cache instance across all render attempts, even if the component
10727+
// is interrupted before it commits.
10728+
//
10729+
// If an update is interrupted, either because it suspended or
10730+
// because of another update, we can reuse the memoized computations
10731+
// from the previous attempt. We can do this because the React
10732+
// Compiler performs atomic writes to the memo cache, i.e. it will
10733+
// not record the inputs to a memoization without also recording its
10734+
// output.
10735+
//
10736+
// This gives us a form of "resuming" within components and hooks.
10737+
//
10738+
// This only works when updating a component that already mounted.
10739+
// It has no impact during initial render, because the memo cache is
10740+
// stored on the fiber, and since we have not implemented resuming
10741+
// for fibers, it's always a fresh memo cache, anyway.
10742+
//
10743+
// However, this alone is pretty useful — it happens whenever you
10744+
// update the UI with fresh data after a mutation/action, which is
10745+
// extremely common in a Suspense-driven (e.g. RSC or Relay) app.
10746+
data: // Clone the memo cache before each render (copy-on-write)
10747+
currentMemoCache.data.map(function (array) {
1072510748
return array.slice();
1072610749
}),
1072710750
index: 0
@@ -26452,7 +26475,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition
2645226475
return root;
2645326476
}
2645426477

26455-
var ReactVersion = '19.0.0-canary-4c1bc958';
26478+
var ReactVersion = '19.0.0-canary-852c4872';
2645626479

2645726480
/*
2645826481
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol

0 commit comments

Comments
 (0)