From 9005d93e3225fc0dbaaae93a299d0edf6c628d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 7 Jul 2025 06:42:23 -0700 Subject: [PATCH] Optimize performance.mark and performance.measure (#52429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52429 Changelog: [internal] This implements a significant optimization for `performance.mark` and `performance.measure`. After these changes, they take 1/3 of the time they took before. We've seen these methods show up too often in traces (in the Hermes sampling profiler) so this should significantly reduce their overhead. I found out that most of the time spent in `performance.mark` and `performance.measure` was creating the `PerformanceMark` and `PerformanceMeasure` instances returned by those methods. I applied the same playbook I did for `ReactNativeElement` (avoiding private fields and `super()` calls, which yielded these wins. * Before | (index) | Task name | Latency average (ns) | Latency median (ns) | Throughput average (ops/s) | Throughput median (ops/s) | Samples | | ------- | --------------------------------------------------------- | -------------------- | ------------------- | -------------------------- | ------------------------- | ------- | | 0 | 'mark (default)' | '5579.80 ± 0.32%' | '5479.00' | '181368 ± 0.02%' | '182515' | 179218 | | 1 | 'mark (with custom startTime)' | '5759.72 ± 0.99%' | '5648.00' | '176162 ± 0.02%' | '177054' | 173620 | | 2 | 'measure (with start and end timestamps)' | '6506.38 ± 0.34%' | '6390.00' | '155503 ± 0.02%' | '156495' | 153696 | | 3 | 'measure (with mark names)' | '6770.94 ± 0.72%' | '6620.00' | '149833 ± 0.03%' | '151057' | 147691 | | 4 | 'clearMarks' | '785.89 ± 0.07%' | '771.00' | '1291356 ± 0.01%' | '1297017' | 1272442 | | 5 | 'clearMeasures' | '777.98 ± 0.06%' | '761.00' | '1303362 ± 0.01%' | '1314060' | 1285383 | | 6 | 'mark + clearMarks' | '5995.34 ± 1.37%' | '5779.00' | '171874 ± 0.03%' | '173040' | 166797 | | 7 | 'measure + clearMeasures (with start and end timestamps)' | '7040.28 ± 0.57%' | '6830.00' | '145289 ± 0.03%' | '146413' | 142040 | | 8 | 'measure + clearMeasures (with mark names)' | '7184.43 ± 0.40%' | '6990.00' | '141809 ± 0.03%' | '143062' | 139190 | * After | (index) | Task name | Latency average (ns) | Latency median (ns) | Throughput average (ops/s) | Throughput median (ops/s) | Samples | | ------- | --------------------------------------------------------- | -------------------- | ------------------- | -------------------------- | ------------------------- | ------- | | 0 | 'mark (default)' | '1678.19 ± 0.73%' | '1633.00' | '607139 ± 0.01%' | '612370' | 595882 | | 1 | 'mark (with custom startTime)' | '1920.23 ± 1.30%' | '1843.00' | '538217 ± 0.01%' | '542594' | 520772 | | 2 | 'measure (with start and end timestamps)' | '2651.72 ± 0.94%' | '2554.00' | '388312 ± 0.02%' | '391543' | 377114 | | 3 | 'measure (with mark names)' | '2815.84 ± 0.75%' | '2744.00' | '362303 ± 0.02%' | '364431' | 355134 | | 4 | 'clearMarks' | '743.82 ± 0.06%' | '731.00' | '1363190 ± 0.01%' | '1367989' | 1344417 | | 5 | 'clearMeasures' | '776.69 ± 0.07%' | '761.00' | '1306563 ± 0.01%' | '1314060' | 1287512 | | 6 | 'mark + clearMarks' | '2043.97 ± 1.26%' | '1973.00' | '504750 ± 0.01%' | '506842' | 489801 | | 7 | 'measure + clearMeasures (with start and end timestamps)' | '3048.39 ± 0.87%' | '2965.00' | '335285 ± 0.02%' | '337268' | 328042 | | 8 | 'measure + clearMeasures (with mark names)' | '3132.75 ± 0.80%' | '3065.00' | '324365 ± 0.02%' | '326264' | 319209 | Reviewed By: huntie Differential Revision: D77790874 fbshipit-source-id: baf7aca07d281fef4373956d125c63f006fab592 --- .../webapis/performance/PerformanceEntry.js | 36 +++++----- .../private/webapis/performance/UserTiming.js | 68 ++++++++++++++++--- 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/packages/react-native/src/private/webapis/performance/PerformanceEntry.js b/packages/react-native/src/private/webapis/performance/PerformanceEntry.js index 76daf969c03..f2ddfceda55 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceEntry.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceEntry.js @@ -29,10 +29,14 @@ export type PerformanceEntryJSON = { }; export class PerformanceEntry { - #name: string; - #entryType: PerformanceEntryType; - #startTime: DOMHighResTimeStamp; - #duration: DOMHighResTimeStamp; + // We don't use private fields because they're significantly slower to + // initialize on construction and to access. + // We also need these to be protected so they can be initialized in subclasses + // where we avoid calling `super()` for performance reasons. + __name: string; + __entryType: PerformanceEntryType; + __startTime: DOMHighResTimeStamp; + __duration: DOMHighResTimeStamp; constructor(init: { name: string, @@ -40,34 +44,34 @@ export class PerformanceEntry { startTime: DOMHighResTimeStamp, duration: DOMHighResTimeStamp, }) { - this.#name = init.name; - this.#entryType = init.entryType; - this.#startTime = init.startTime; - this.#duration = init.duration; + this.__name = init.name; + this.__entryType = init.entryType; + this.__startTime = init.startTime; + this.__duration = init.duration; } get name(): string { - return this.#name; + return this.__name; } get entryType(): PerformanceEntryType { - return this.#entryType; + return this.__entryType; } get startTime(): DOMHighResTimeStamp { - return this.#startTime; + return this.__startTime; } get duration(): DOMHighResTimeStamp { - return this.#duration; + return this.__duration; } toJSON(): PerformanceEntryJSON { return { - name: this.#name, - entryType: this.#entryType, - startTime: this.#startTime, - duration: this.#duration, + name: this.__name, + entryType: this.__entryType, + startTime: this.__startTime, + duration: this.__duration, }; } } diff --git a/packages/react-native/src/private/webapis/performance/UserTiming.js b/packages/react-native/src/private/webapis/performance/UserTiming.js index 6809f3ca5ca..09aaa140d38 100644 --- a/packages/react-native/src/private/webapis/performance/UserTiming.js +++ b/packages/react-native/src/private/webapis/performance/UserTiming.js @@ -29,9 +29,12 @@ export type PerformanceMeasureInit = { duration: DOMHighResTimeStamp, }; -export class PerformanceMark extends PerformanceEntry { - #detail: DetailType; +class PerformanceMarkTemplate extends PerformanceEntry { + // We don't use private fields because they're significantly slower to + // initialize on construction and to access. + _detail: DetailType; + // This constructor isn't really used. See `PerformanceMark` below. constructor(markName: string, markOptions?: PerformanceMarkOptions) { super({ name: markName, @@ -41,18 +44,46 @@ export class PerformanceMark extends PerformanceEntry { }); if (markOptions) { - this.#detail = markOptions.detail; + this._detail = markOptions.detail; } } get detail(): DetailType { - return this.#detail; + return this._detail; } } -export class PerformanceMeasure extends PerformanceEntry { - #detail: DetailType; +// This is the real value we're exporting where we define the class a function +// so we don't need to call `super()` and we can avoid the performance penalty +// of the current code transpiled with Babel. +// We should remove this when we have built-in support for classes in the +// runtime. +export const PerformanceMark: typeof PerformanceMarkTemplate = + // $FlowExpectedError[incompatible-type] + function PerformanceMark( + this: PerformanceMarkTemplate, + markName: string, + markOptions?: PerformanceMarkOptions, + ) { + this.__name = markName; + this.__entryType = 'mark'; + this.__startTime = markOptions?.startTime ?? performance.now(); + this.__duration = 0; + if (markOptions) { + this._detail = markOptions.detail; + } + }; + +// $FlowExpectedError[prop-missing] +PerformanceMark.prototype = PerformanceMarkTemplate.prototype; + +class PerformanceMeasureTemplate extends PerformanceEntry { + // We don't use private fields because they're significantly slower to + // initialize on construction and to access. + _detail: DetailType; + + // This constructor isn't really used. See `PerformanceMeasure` below. constructor(measureName: string, measureOptions: PerformanceMeasureInit) { super({ name: measureName, @@ -62,11 +93,32 @@ export class PerformanceMeasure extends PerformanceEntry { }); if (measureOptions) { - this.#detail = measureOptions.detail; + this._detail = measureOptions.detail; } } get detail(): DetailType { - return this.#detail; + return this._detail; } } + +// We do the same here as we do for `PerformanceMark` for performance reasons. +export const PerformanceMeasure: typeof PerformanceMeasureTemplate = + // $FlowExpectedError[incompatible-type] + function PerformanceMeasure( + this: PerformanceMeasureTemplate, + measureName: string, + measureOptions: PerformanceMeasureInit, + ) { + this.__name = measureName; + this.__entryType = 'measure'; + this.__startTime = measureOptions.startTime; + this.__duration = measureOptions.duration; + + if (measureOptions) { + this._detail = measureOptions.detail; + } + }; + +// $FlowExpectedError[prop-missing] +PerformanceMeasure.prototype = PerformanceMeasureTemplate.prototype;