Skip to content

perf(transformer/jsx): pre-allocate enough memory for arguments of createElement/jsx function#9915

Merged
graphite-app[bot] merged 1 commit into
mainfrom
03-20-perf_transformer_jsx_pre-allocate_enough_memory_for_arguments_of_createelement_jsx_function
Mar 21, 2025
Merged

perf(transformer/jsx): pre-allocate enough memory for arguments of createElement/jsx function#9915
graphite-app[bot] merged 1 commit into
mainfrom
03-20-perf_transformer_jsx_pre-allocate_enough_memory_for_arguments_of_createelement_jsx_function

Conversation

@Dunqing

@Dunqing Dunqing commented Mar 20, 2025

Copy link
Copy Markdown
Member

This is a hot path for JSX transform, allocating sufficient memory can avoid reserving in push, every bit of optimization that adds up can be huge.

@Dunqing Dunqing requested a review from overlookmotel as a code owner March 20, 2025 06:39

Dunqing commented Mar 20, 2025

Copy link
Copy Markdown
Member Author

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • 0-merge - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions github-actions Bot added A-transformer Area - Transformer / Transpiler C-performance Category - Solution not expected to change functional behavior, only performance labels Mar 20, 2025
@codspeed-hq

codspeed-hq Bot commented Mar 20, 2025

Copy link
Copy Markdown

CodSpeed Instrumentation Performance Report

Merging #9915 will not alter performance

Comparing 03-20-perf_transformer_jsx_pre-allocate_enough_memory_for_arguments_of_createelement_jsx_function (374050a) with main (fbb268a)

Summary

✅ 33 untouched benchmarks

@Dunqing

Dunqing commented Mar 20, 2025

Copy link
Copy Markdown
Member Author
image

@overlookmotel overlookmotel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cowabunga!

@overlookmotel overlookmotel added the 0-merge Merge with Graphite Merge Queue label Mar 21, 2025

overlookmotel commented Mar 21, 2025

Copy link
Copy Markdown
Member

Merge activity

  • Mar 21, 4:43 AM EDT: The merge label '0-merge' was detected. This PR will be added to the Graphite merge queue once it meets the requirements.
  • Mar 21, 4:43 AM EDT: A user added this pull request to the Graphite merge queue.
  • Mar 21, 5:11 AM EDT: A user merged this pull request with the Graphite merge queue.

…`createElement`/`jsx` function (#9915)

This is a hot path for JSX transform, allocating sufficient memory can avoid reserving in `push`, every bit of optimization that adds up can be huge.
@graphite-app graphite-app Bot force-pushed the 03-20-perf_transformer_jsx_pre-allocate_enough_memory_for_arguments_of_createelement_jsx_function branch from 831c8f2 to 374050a Compare March 21, 2025 08:55
@graphite-app graphite-app Bot merged commit 374050a into main Mar 21, 2025
@graphite-app graphite-app Bot deleted the 03-20-perf_transformer_jsx_pre-allocate_enough_memory_for_arguments_of_createelement_jsx_function branch March 21, 2025 09:11
@graphite-app graphite-app Bot removed the 0-merge Merge with Graphite Merge Queue label Mar 21, 2025
camc314 added a commit that referenced this pull request Jun 16, 2026
## Memory reduction: fewer arena reallocations on JSX

`transform_jsx` built the `React.createElement` / jsx props object in a
**zero-capacity** arena vec
(`ctx.ast.vec()`), regrowing it (4 → 8 → 16 …) as it pushed one property
per attribute. The attribute
count is already known, so pre-sizing the vec to it removes those
regrowths — mirroring `arguments`,
pre-sized a few lines above, and the same pattern as #9915.

Measured with `cargo allocs` (`TestFiles::complicated`). **Arena
reallocations drop on every JSX file;
nothing else changes** (system allocs and the arena-alloc count are
essentially flat):

| file | arena reallocs: before → after | **reduction** |
| --- | --- | --- |
| App.tsx | 77 → 25 | **−52 (−68%)** |
| kitchen-sink.tsx | 517 → 304 | **−213 (−41%)** |
| RadixUIAdoptionSection.jsx | 24 → 13 | **−11 (−46%)** |

Each eliminated realloc is a buffer grow + memcpy of the existing
elements, so this is real
work removed on the JSX hot path, not just bytes.

<details><summary>Raw <code>allocs_transformer.snap</code> diff (updated
in this PR)</summary>

```diff
-App.tsx                    | 415.34 kB      ||             25 |              2 ||            719 |             77
+App.tsx                    | 415.34 kB      ||             25 |              2 ||            719 |             25
-RadixUIAdoptionSection.jsx | 2.52 kB        ||             21 |              2 ||            259 |             24
+RadixUIAdoptionSection.jsx | 2.52 kB        ||             21 |              2 ||            259 |             13
-kitchen-sink.tsx           | 732.90 kB      ||            184 |              3 ||           6689 |            517
+kitchen-sink.tsx           | 732.90 kB      ||            184 |              3 ||           6690 |            304
```

(Non-JSX files unchanged. The lone `+1` arena alloc on kitchen-sink is
an eager 1-slot allocation for
a rare element whose attributes are all filtered out — negligible
against −213 reallocs.)

</details>

## Change

```rust
let mut properties =
    ctx.ast.vec_with_capacity(opening_element.as_ref().map_or(0, |e| e.attributes.len()));
```

Capacity-only: the vec's contents, push order, and emitted output are
unchanged — only the initial
allocation differs. CodSpeed (instructions) may pick up the saved
memcpys on JSX-heavy input, but this
is framed as a memory/allocation win.

## Behavior-preserving

`cargo test -p oxc_transformer` passes. The change is a capacity hint
and cannot alter the produced AST.

---

Prepared with AI assistance.

Co-authored-by: Cameron <cameron.clark@hey.com>
camc314 added a commit that referenced this pull request Jul 3, 2026
## Memory reduction: fewer arena reallocations on JSX

`transform_jsx` built the `React.createElement` / jsx props object in a
**zero-capacity** arena vec
(`ctx.ast.vec()`), regrowing it (4 → 8 → 16 …) as it pushed one property
per attribute. The attribute
count is already known, so pre-sizing the vec to it removes those
regrowths — mirroring `arguments`,
pre-sized a few lines above, and the same pattern as #9915.

Measured with `cargo allocs` (`TestFiles::complicated`). **Arena
reallocations drop on every JSX file;
nothing else changes** (system allocs and the arena-alloc count are
essentially flat):

| file | arena reallocs: before → after | **reduction** |
| --- | --- | --- |
| App.tsx | 77 → 25 | **−52 (−68%)** |
| kitchen-sink.tsx | 517 → 304 | **−213 (−41%)** |
| RadixUIAdoptionSection.jsx | 24 → 13 | **−11 (−46%)** |

Each eliminated realloc is a buffer grow + memcpy of the existing
elements, so this is real
work removed on the JSX hot path, not just bytes.

<details><summary>Raw <code>allocs_transformer.snap</code> diff (updated
in this PR)</summary>

```diff
-App.tsx                    | 415.34 kB      ||             25 |              2 ||            719 |             77
+App.tsx                    | 415.34 kB      ||             25 |              2 ||            719 |             25
-RadixUIAdoptionSection.jsx | 2.52 kB        ||             21 |              2 ||            259 |             24
+RadixUIAdoptionSection.jsx | 2.52 kB        ||             21 |              2 ||            259 |             13
-kitchen-sink.tsx           | 732.90 kB      ||            184 |              3 ||           6689 |            517
+kitchen-sink.tsx           | 732.90 kB      ||            184 |              3 ||           6690 |            304
```

(Non-JSX files unchanged. The lone `+1` arena alloc on kitchen-sink is
an eager 1-slot allocation for
a rare element whose attributes are all filtered out — negligible
against −213 reallocs.)

</details>

## Change

```rust
let mut properties =
    ctx.ast.vec_with_capacity(opening_element.as_ref().map_or(0, |e| e.attributes.len()));
```

Capacity-only: the vec's contents, push order, and emitted output are
unchanged — only the initial
allocation differs. CodSpeed (instructions) may pick up the saved
memcpys on JSX-heavy input, but this
is framed as a memory/allocation win.

## Behavior-preserving

`cargo test -p oxc_transformer` passes. The change is a capacity hint
and cannot alter the produced AST.

---

Prepared with AI assistance.

Co-authored-by: Cameron <cameron.clark@hey.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-transformer Area - Transformer / Transpiler C-performance Category - Solution not expected to change functional behavior, only performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants