Optimize performance.mark and performance.measure (#52429)

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
This commit is contained in:
Rubén Norte
2025-07-07 06:42:23 -07:00
committed by Facebook GitHub Bot
parent 74c03b67e3
commit 9005d93e32
2 changed files with 80 additions and 24 deletions
@@ -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,
};
}
}
@@ -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;