Skip to content

Commit

Permalink
[Experiment] Reuse memo cache after interruption (#28878)
Browse files Browse the repository at this point in the history
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 [ea26e38](ea26e38)
  • Loading branch information
acdlite committed Apr 19, 2024
1 parent a6449bd commit 2b01d44
Show file tree
Hide file tree
Showing 28 changed files with 371 additions and 96 deletions.
3 changes: 2 additions & 1 deletion compiled/facebook-www/JSXDEVRuntime-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing,
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
enableRenderableContext = dynamicFeatureFlags.enableRenderableContext,
enableRefAsProp = dynamicFeatureFlags.enableRefAsProp,
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, false is used for a new modern build.
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses;
// On WWW, false is used for a new modern build.
// because JSX is an extremely hot path.

var disableStringRefs = false;
Expand Down
3 changes: 2 additions & 1 deletion compiled/facebook-www/JSXDEVRuntime-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing,
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
enableRenderableContext = dynamicFeatureFlags.enableRenderableContext,
enableRefAsProp = dynamicFeatureFlags.enableRefAsProp,
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, true is used for a new modern build.
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses;
// On WWW, true is used for a new modern build.
// because JSX is an extremely hot path.

var disableStringRefs = false;
Expand Down
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
446aa9a632670eb9a373d897309452e24c10c55e
ea26e38e33bffeba1ecc42688d7e8cd7e0da1c02
5 changes: 3 additions & 2 deletions compiled/facebook-www/React-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ if (
) {
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
}
var ReactVersion = '19.0.0-www-classic-a296fa2c';
var ReactVersion = '19.0.0-www-classic-8ac39151';

// ATTENTION
// When adding new symbols to this file,
Expand Down Expand Up @@ -429,7 +429,8 @@ var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing,
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
enableRenderableContext = dynamicFeatureFlags.enableRenderableContext,
enableRefAsProp = dynamicFeatureFlags.enableRefAsProp,
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, false is used for a new modern build.
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses;
// On WWW, false is used for a new modern build.
// because JSX is an extremely hot path.

var disableStringRefs = false;
Expand Down
5 changes: 3 additions & 2 deletions compiled/facebook-www/React-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ if (
) {
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
}
var ReactVersion = '19.0.0-www-modern-fe830ed2';
var ReactVersion = '19.0.0-www-modern-f6026193';

// ATTENTION
// When adding new symbols to this file,
Expand Down Expand Up @@ -429,7 +429,8 @@ var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing,
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
enableRenderableContext = dynamicFeatureFlags.enableRenderableContext,
enableRefAsProp = dynamicFeatureFlags.enableRefAsProp,
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, true is used for a new modern build.
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses;
// On WWW, true is used for a new modern build.
// because JSX is an extremely hot path.

var disableStringRefs = false;
Expand Down
30 changes: 27 additions & 3 deletions compiled/facebook-www/ReactART-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function _assertThisInitialized(self) {
return self;
}

var ReactVersion = '19.0.0-www-classic-94931f64';
var ReactVersion = '19.0.0-www-classic-d14dfff2';

var LegacyRoot = 0;
var ConcurrentRoot = 1;
Expand Down Expand Up @@ -161,7 +161,8 @@ var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing,
enableInfiniteRenderLoopDetection = dynamicFeatureFlags.enableInfiniteRenderLoopDetection,
enableRenderableContext = dynamicFeatureFlags.enableRenderableContext,
enableRefAsProp = dynamicFeatureFlags.enableRefAsProp,
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, false is used for a new modern build.
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses,
enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache; // On WWW, false is used for a new modern build.
var enableProfilerTimer = true;
var enableProfilerCommitHooks = true;
var enableProfilerNestedUpdatePhase = true;
Expand Down Expand Up @@ -8403,7 +8404,30 @@ function useMemoCache(size) {

if (currentMemoCache != null) {
memoCache = {
data: currentMemoCache.data.map(function (array) {
// When enableNoCloningMemoCache is 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.
data: enableNoCloningMemoCache ? currentMemoCache.data : // Clone the memo cache before each render (copy-on-write)
currentMemoCache.data.map(function (array) {
return array.slice();
}),
index: 0
Expand Down
30 changes: 27 additions & 3 deletions compiled/facebook-www/ReactART-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function _assertThisInitialized(self) {
return self;
}

var ReactVersion = '19.0.0-www-modern-c57fe631';
var ReactVersion = '19.0.0-www-modern-41846fdd';

var LegacyRoot = 0;
var ConcurrentRoot = 1;
Expand Down Expand Up @@ -161,7 +161,8 @@ var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing,
enableInfiniteRenderLoopDetection = dynamicFeatureFlags.enableInfiniteRenderLoopDetection,
enableRenderableContext = dynamicFeatureFlags.enableRenderableContext,
enableRefAsProp = dynamicFeatureFlags.enableRefAsProp,
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, true is used for a new modern build.
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses,
enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache; // On WWW, true is used for a new modern build.
var enableProfilerTimer = true;
var enableProfilerCommitHooks = true;
var enableProfilerNestedUpdatePhase = true;
Expand Down Expand Up @@ -8192,7 +8193,30 @@ function useMemoCache(size) {

if (currentMemoCache != null) {
memoCache = {
data: currentMemoCache.data.map(function (array) {
// When enableNoCloningMemoCache is 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.
data: enableNoCloningMemoCache ? currentMemoCache.data : // Clone the memo cache before each render (copy-on-write)
currentMemoCache.data.map(function (array) {
return array.slice();
}),
index: 0
Expand Down
13 changes: 8 additions & 5 deletions compiled/facebook-www/ReactART-prod.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ var ReactSharedInternals =
enableRefAsProp = dynamicFeatureFlags.enableRefAsProp,
disableDefaultPropsExceptForClasses =
dynamicFeatureFlags.disableDefaultPropsExceptForClasses,
enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache,
REACT_ELEMENT_TYPE = Symbol.for("react.element"),
REACT_PORTAL_TYPE = Symbol.for("react.portal"),
REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"),
Expand Down Expand Up @@ -2739,9 +2740,11 @@ function useMemoCache(size) {
((current = current.memoCache),
null != current &&
(memoCache = {
data: current.data.map(function (array) {
return array.slice();
}),
data: enableNoCloningMemoCache
? current.data
: current.data.map(function (array) {
return array.slice();
}),
index: 0
})));
}
Expand Down Expand Up @@ -10619,7 +10622,7 @@ var slice = Array.prototype.slice,
return null;
},
bundleType: 0,
version: "19.0.0-www-classic-e5fcf28b",
version: "19.0.0-www-classic-4cca10ad",
rendererPackageName: "react-art"
};
var internals$jscomp$inline_1322 = {
Expand Down Expand Up @@ -10650,7 +10653,7 @@ var internals$jscomp$inline_1322 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-www-classic-e5fcf28b"
reconcilerVersion: "19.0.0-www-classic-4cca10ad"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1323 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand Down
13 changes: 8 additions & 5 deletions compiled/facebook-www/ReactART-prod.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ var ReactSharedInternals =
enableRefAsProp = dynamicFeatureFlags.enableRefAsProp,
disableDefaultPropsExceptForClasses =
dynamicFeatureFlags.disableDefaultPropsExceptForClasses,
enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache,
REACT_ELEMENT_TYPE = Symbol.for("react.element"),
REACT_PORTAL_TYPE = Symbol.for("react.portal"),
REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"),
Expand Down Expand Up @@ -2537,9 +2538,11 @@ function useMemoCache(size) {
((current = current.memoCache),
null != current &&
(memoCache = {
data: current.data.map(function (array) {
return array.slice();
}),
data: enableNoCloningMemoCache
? current.data
: current.data.map(function (array) {
return array.slice();
}),
index: 0
})));
}
Expand Down Expand Up @@ -10098,7 +10101,7 @@ var slice = Array.prototype.slice,
return null;
},
bundleType: 0,
version: "19.0.0-www-modern-20a4f736",
version: "19.0.0-www-modern-c36cdef3",
rendererPackageName: "react-art"
};
var internals$jscomp$inline_1307 = {
Expand Down Expand Up @@ -10129,7 +10132,7 @@ var internals$jscomp$inline_1307 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-www-modern-20a4f736"
reconcilerVersion: "19.0.0-www-modern-c36cdef3"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1308 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand Down
30 changes: 27 additions & 3 deletions compiled/facebook-www/ReactDOM-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ var enableTrustedTypesIntegration = dynamicFeatureFlags.enableTrustedTypesIntegr
enableRenderableContext = dynamicFeatureFlags.enableRenderableContext,
enableRefAsProp = dynamicFeatureFlags.enableRefAsProp,
favorSafetyOverHydrationPerf = dynamicFeatureFlags.favorSafetyOverHydrationPerf,
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, false is used for a new modern build.
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses,
enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache; // On WWW, false is used for a new modern build.
var enableProfilerTimer = true;
var enableProfilerCommitHooks = true;
var enableProfilerNestedUpdatePhase = true;
Expand Down Expand Up @@ -12043,7 +12044,30 @@ function useMemoCache(size) {

if (currentMemoCache != null) {
memoCache = {
data: currentMemoCache.data.map(function (array) {
// When enableNoCloningMemoCache is 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.
data: enableNoCloningMemoCache ? currentMemoCache.data : // Clone the memo cache before each render (copy-on-write)
currentMemoCache.data.map(function (array) {
return array.slice();
}),
index: 0
Expand Down Expand Up @@ -30808,7 +30832,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition
return root;
}

var ReactVersion = '19.0.0-www-classic-bf2e83d8';
var ReactVersion = '19.0.0-www-classic-3bfa22a0';

function createPortal$1(children, containerInfo, // TODO: figure out the API for cross-renderer implementation.
implementation) {
Expand Down
30 changes: 27 additions & 3 deletions compiled/facebook-www/ReactDOM-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ var enableTrustedTypesIntegration = dynamicFeatureFlags.enableTrustedTypesIntegr
enableRenderableContext = dynamicFeatureFlags.enableRenderableContext,
enableRefAsProp = dynamicFeatureFlags.enableRefAsProp,
favorSafetyOverHydrationPerf = dynamicFeatureFlags.favorSafetyOverHydrationPerf,
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, true is used for a new modern build.
disableDefaultPropsExceptForClasses = dynamicFeatureFlags.disableDefaultPropsExceptForClasses,
enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache; // On WWW, true is used for a new modern build.
var enableProfilerTimer = true;
var enableProfilerCommitHooks = true;
var enableProfilerNestedUpdatePhase = true;
Expand Down Expand Up @@ -18179,7 +18180,30 @@ function useMemoCache(size) {

if (currentMemoCache != null) {
memoCache = {
data: currentMemoCache.data.map(function (array) {
// When enableNoCloningMemoCache is 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.
data: enableNoCloningMemoCache ? currentMemoCache.data : // Clone the memo cache before each render (copy-on-write)
currentMemoCache.data.map(function (array) {
return array.slice();
}),
index: 0
Expand Down Expand Up @@ -38715,7 +38739,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition
return root;
}

var ReactVersion = '19.0.0-www-modern-806cc996';
var ReactVersion = '19.0.0-www-modern-b1784c6b';

function createPortal$1(children, containerInfo, // TODO: figure out the API for cross-renderer implementation.
implementation) {
Expand Down
15 changes: 9 additions & 6 deletions compiled/facebook-www/ReactDOM-prod.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ var ReactSharedInternals =
dynamicFeatureFlags.favorSafetyOverHydrationPerf,
disableDefaultPropsExceptForClasses =
dynamicFeatureFlags.disableDefaultPropsExceptForClasses,
enableNoCloningMemoCache = dynamicFeatureFlags.enableNoCloningMemoCache,
REACT_ELEMENT_TYPE = Symbol.for("react.element"),
REACT_PORTAL_TYPE = Symbol.for("react.portal"),
REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"),
Expand Down Expand Up @@ -3448,9 +3449,11 @@ function useMemoCache(size) {
((current = current.memoCache),
null != current &&
(memoCache = {
data: current.data.map(function (array) {
return array.slice();
}),
data: enableNoCloningMemoCache
? current.data
: current.data.map(function (array) {
return array.slice();
}),
index: 0
})));
}
Expand Down Expand Up @@ -17033,7 +17036,7 @@ Internals.Events = [
var devToolsConfig$jscomp$inline_1729 = {
findFiberByHostInstance: getClosestInstanceFromNode,
bundleType: 0,
version: "19.0.0-www-classic-38c43cf6",
version: "19.0.0-www-classic-7b6a2c60",
rendererPackageName: "react-dom"
};
var internals$jscomp$inline_2160 = {
Expand Down Expand Up @@ -17063,7 +17066,7 @@ var internals$jscomp$inline_2160 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-www-classic-38c43cf6"
reconcilerVersion: "19.0.0-www-classic-7b6a2c60"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_2161 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand Down Expand Up @@ -17529,4 +17532,4 @@ exports.useFormState = function (action, initialState, permalink) {
exports.useFormStatus = function () {
return ReactSharedInternals.H.useHostTransitionStatus();
};
exports.version = "19.0.0-www-classic-38c43cf6";
exports.version = "19.0.0-www-classic-7b6a2c60";
Loading

0 comments on commit 2b01d44

Please sign in to comment.