mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Optimize structuredClone (#51250)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51250 Changelog: [internal] Optimizes the implementation of `structuredClone`. Before: | (index) | Task name | Latency average (ns) | Latency median (ns) | Throughput average (ops/s) | Throughput median (ops/s) | Samples | | ------- | ------------------------ | -------------------- | ------------------- | -------------------------- | ------------------------- | ------- | | 0 | 'clone a string' | '955.81 ± 1.45%' | '902.00' | '1095925 ± 0.01%' | '1108647' | 1046239 | | 1 | 'clone a basic array' | '7684.79 ± 0.76%' | '7542.00' | '131980 ± 0.02%' | '132591' | 130128 | | 2 | 'clone a basic object' | '6286.35 ± 0.53%' | '6179.00' | '161179 ± 0.02%' | '161838' | 159075 | | 3 | 'clone a complex object' | '22446.96 ± 0.32%' | '22223.00' | '44819 ± 0.03%' | '44998' | 44550 | After: | (index) | Task name | Latency average (ns) | Latency median (ns) | Throughput average (ops/s) | Throughput median (ops/s) | Samples | | ------- | ------------------------ | -------------------- | ------------------- | -------------------------- | ------------------------- | ------- | | 0 | 'clone a string' | '793.81 ± 0.09%' | '781.00' | '1270316 ± 0.01%' | '1280410' | 1259755 | | 1 | 'clone a basic array' | '5079.81 ± 2.13%' | '4798.00' | '206235 ± 0.03%' | '208420' | 196858 | | 2 | 'clone a basic object' | '3823.37 ± 1.60%' | '3636.00' | '271030 ± 0.03%' | '275028' | 261550 | | 3 | 'clone a complex object' | '13095.13 ± 2.04%' | '12499.00' | '79701 ± 0.03%' | '80006' | 76365 | Reviewed By: hoxyq Differential Revision: D74576514 fbshipit-source-id: f452dcfca6398dbfa40c5a41b77b0157777fa59b
This commit is contained in:
committed by
Facebook GitHub Bot
parent
f7327bddb5
commit
caaa5c93fb
+77
-61
@@ -21,7 +21,18 @@ const VALID_ERROR_NAMES = new Set([
|
||||
'URIError',
|
||||
]);
|
||||
|
||||
function structuredCloneInternal<T>(value: T, memory: Map<mixed, mixed>): T {
|
||||
const BASIC_CONSTRUCTORS = [Number, String, Boolean, Date];
|
||||
|
||||
const ObjectPrototype = Object.prototype;
|
||||
|
||||
// Technicall the memory value should be a parameter in
|
||||
// `structuredCloneInternal` but as an optimization we can reuse the same map
|
||||
// and avoid allocating a new one in every call to `structuredClone`.
|
||||
// This is safe because we don't invoke user code in `structuredClone`, so at
|
||||
// any given point we only have one memory object alive anyway.
|
||||
const memory: Map<mixed, mixed> = new Map();
|
||||
|
||||
function structuredCloneInternal<T>(value: T): T {
|
||||
// Handles `null` and `undefined`.
|
||||
if (value == null) {
|
||||
return value;
|
||||
@@ -51,54 +62,68 @@ function structuredCloneInternal<T>(value: T, memory: Map<mixed, mixed>): T {
|
||||
return memory.get(value);
|
||||
}
|
||||
|
||||
// Known non-serializable objects
|
||||
// TODO: Handle this more holistically.
|
||||
if (
|
||||
value instanceof WeakMap ||
|
||||
value instanceof WeakSet ||
|
||||
value instanceof Promise
|
||||
) {
|
||||
throw new DOMException(
|
||||
`Failed to execute 'structuredClone' on 'Window': ${String(value)} could not be cloned.`,
|
||||
'DataCloneError',
|
||||
);
|
||||
}
|
||||
|
||||
// Handles primitive wrappers.
|
||||
|
||||
if (value instanceof Number) {
|
||||
// eslint-disable-next-line no-new-wrappers
|
||||
const result = new Number(value);
|
||||
// Handles arrays.
|
||||
if (Array.isArray(value)) {
|
||||
const result = [];
|
||||
memory.set(value, result);
|
||||
|
||||
for (const key of Object.keys(value)) {
|
||||
result[key] = structuredCloneInternal(value[key]);
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-return] we know result is T
|
||||
return result;
|
||||
}
|
||||
|
||||
if (value instanceof Boolean) {
|
||||
// eslint-disable-next-line no-new-wrappers
|
||||
const result = new Boolean(value);
|
||||
// Simple object fast path
|
||||
// $FlowIssue[prop-missing] Why doesn't Flow know about Object.prototype?
|
||||
if (Object.getPrototypeOf(value) === ObjectPrototype) {
|
||||
const result = {};
|
||||
memory.set(value, result);
|
||||
|
||||
for (const key of Object.keys(value)) {
|
||||
// $FlowExpectedError[prop-missing]
|
||||
result[key] = structuredCloneInternal(value[key]);
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-return] we know result is T
|
||||
return result;
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
// eslint-disable-next-line no-new-wrappers
|
||||
const result = new String(value);
|
||||
// Handles complex types (typeof === 'object').
|
||||
|
||||
for (const Cls of BASIC_CONSTRUCTORS) {
|
||||
if (value instanceof Cls) {
|
||||
const result = new Cls(value);
|
||||
memory.set(value, result);
|
||||
// $FlowExpectedError[incompatible-return] we know result is T
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (value instanceof Map) {
|
||||
const result = new Map<mixed, mixed>();
|
||||
memory.set(value, result);
|
||||
|
||||
for (const [innerKey, innerValue] of value) {
|
||||
result.set(
|
||||
structuredCloneInternal(innerKey),
|
||||
structuredCloneInternal(innerValue),
|
||||
);
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-return] we know result is T
|
||||
return result;
|
||||
}
|
||||
|
||||
// Handles remaining known objects.
|
||||
|
||||
if (value instanceof Date) {
|
||||
const result = new Date(value);
|
||||
if (value instanceof Set) {
|
||||
const result = new Set<mixed>();
|
||||
memory.set(value, result);
|
||||
|
||||
for (const innerValue of value) {
|
||||
result.add(structuredCloneInternal(innerValue));
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-return] we know result is T
|
||||
return result;
|
||||
}
|
||||
@@ -121,33 +146,6 @@ function structuredCloneInternal<T>(value: T, memory: Map<mixed, mixed>): T {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (value instanceof Map) {
|
||||
const result = new Map<mixed, mixed>();
|
||||
memory.set(value, result);
|
||||
|
||||
for (const [innerKey, innerValue] of value) {
|
||||
result.set(
|
||||
structuredCloneInternal(innerKey, memory),
|
||||
structuredCloneInternal(innerValue, memory),
|
||||
);
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-return] we know result is T
|
||||
return result;
|
||||
}
|
||||
|
||||
if (value instanceof Set) {
|
||||
const result = new Set<mixed>();
|
||||
memory.set(value, result);
|
||||
|
||||
for (const innerValue of value) {
|
||||
result.add(structuredCloneInternal(innerValue, memory));
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-return] we know result is T
|
||||
return result;
|
||||
}
|
||||
|
||||
if (value instanceof RegExp) {
|
||||
const result = new RegExp(value.source, value.flags);
|
||||
memory.set(value, result);
|
||||
@@ -156,17 +154,31 @@ function structuredCloneInternal<T>(value: T, memory: Map<mixed, mixed>): T {
|
||||
return result;
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-type] result will be T
|
||||
const result: T = Array.isArray(value) ? [] : {};
|
||||
// Known non-serializable objects.
|
||||
// TODO: Handle this more holistically
|
||||
if (
|
||||
value instanceof WeakMap ||
|
||||
value instanceof WeakSet ||
|
||||
value instanceof Promise
|
||||
) {
|
||||
throw new DOMException(
|
||||
`Failed to execute 'structuredClone' on 'Window': ${String(value)} could not be cloned.`,
|
||||
'DataCloneError',
|
||||
);
|
||||
}
|
||||
|
||||
// Arbitrary object slow path
|
||||
const result = {};
|
||||
memory.set(value, result);
|
||||
|
||||
// We need to use Object.keys instead of iterating by indices because we
|
||||
// also need to copy arbitrary fields set in the array.
|
||||
for (const key of Object.keys(value)) {
|
||||
// $FlowExpectedError[incompatible-use]
|
||||
result[key] = structuredCloneInternal(value[key], memory);
|
||||
// $FlowExpectedError[prop-missing]
|
||||
result[key] = structuredCloneInternal(value[key]);
|
||||
}
|
||||
|
||||
// $FlowExpectedError[incompatible-return] we know result is T
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -190,5 +202,9 @@ function structuredCloneInternal<T>(value: T, memory: Map<mixed, mixed>): T {
|
||||
* - it does not support cloning platform objects like `DOMRect` and `DOMException`.
|
||||
*/
|
||||
export default function structuredClone<T>(value: T): T {
|
||||
return structuredCloneInternal(value, new Map());
|
||||
try {
|
||||
return structuredCloneInternal(value);
|
||||
} finally {
|
||||
memory.clear();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user