Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

perf(rome_formatter): Add Buffer.elements #3156

Merged
merged 7 commits into from
Sep 6, 2022
Merged

Conversation

MichaReiser
Copy link
Contributor

@MichaReiser MichaReiser commented Sep 5, 2022

Summary

It turns out, that using the PreambleBuffer in hot code paths is rather expensive. Or in general, nesting Buffers can be rather expensive.

I discovered this after I implemented a prototype of a Flat IR for our formatter (#2571) where this problem becomes more prominent because group, indent etc are no longer "breaking" the buffer chain by writing into a new VecBuffer.

This PR introduces a new Buffer.elements() method that gives access to a slice of all written elements (that are part of this buffer). This allows introspection of the written content very cheaply (may require iteration, but most inspections are only interested in the last element) and in a more natural way (writing custom buffers or even using memoized often feels overly complicated). Another benefit of elements over using inspect or a custom buffer is that it guarantees that rewinding the buffer is correctly taken into account, something that most uses of inspect didn't.

The new elements method allows removing most uses of the PreambleBuffer by doing a simple len comparison or inspecting the slice content.

This PR deletes the unused WillBreakBuffer and HaslLabelBuffer buffers in favour of either Memoized.inspect or using buffer.elements(). We can re-introduce the buffers if it is indeed necessary for some logic to check if any element breaks.

The downside of this is that it slightly leaks the Buffer extraction as it forces the Buffer to now always keep hold of all written elements. For example, this prevents us from having a Printer that prints IR elements as they get formatted. I don't think this is a very practical use case with our current Formatter design as it would mean that the printer has to rewind every time the formatter rewinds... meaning that rewinding will result in more work that must the thrown out the window (and overhead for creating the snapshots).

I even considered changing Formatter to always store a VecBuffer but there are still a few use cases where a custom Buffer can be useful and we should continue supporting those. Buffers on their own aren't a problem, only if they get insanely stacked. For example, it would probably be better to apply a GroupsElementsBuffer once in a world where we have a FlatIR rather than nesting it over and over again.

Test Plan

cargo tetst

The performance win for our nested IR are less impressive. But it improves performance by roughly 20-25% for a flat IR and more improvements are possible once the GroupsElementsBuffer is no longer necessary.

group                                    main                                   no-preamble
-----                                    ----                                   -----------
formatter/checker.ts                     1.05   268.4±20.76ms     9.7 MB/sec    1.00   256.6±20.61ms    10.1 MB/sec
formatter/compiler.js                    1.07    150.9±7.27ms     6.9 MB/sec    1.00    141.2±8.06ms     7.4 MB/sec
formatter/d3.min.js                      1.00    120.0±3.83ms     2.2 MB/sec    1.00    119.7±1.18ms     2.2 MB/sec
formatter/dojo.js                        1.12      8.2±0.57ms     8.3 MB/sec    1.00      7.4±0.03ms     9.3 MB/sec
formatter/ios.d.ts                       1.06   179.7±20.03ms    10.4 MB/sec    1.00   170.0±18.19ms    11.0 MB/sec
formatter/jquery.min.js                  1.02     33.7±0.72ms     2.5 MB/sec    1.00     32.9±1.72ms     2.5 MB/sec
formatter/math.js                        1.13   287.2±15.41ms     2.3 MB/sec    1.00   254.5±23.02ms     2.5 MB/sec
formatter/parser.ts                      1.04      5.4±0.20ms     9.0 MB/sec    1.00      5.2±0.08ms     9.3 MB/sec
formatter/pixi.min.js                    1.00    132.6±2.72ms     3.3 MB/sec    1.04    138.1±6.74ms     3.2 MB/sec
formatter/react-dom.production.min.js    1.06     41.3±2.90ms     2.8 MB/sec    1.00     39.1±0.42ms     2.9 MB/sec
formatter/react.production.min.js        1.03      2.1±0.15ms     3.0 MB/sec    1.00  1990.2±117.48µs     3.1 MB/sec
formatter/router.ts                      1.01      4.2±0.10ms    14.8 MB/sec    1.00      4.1±0.09ms    14.9 MB/sec
formatter/tex-chtml-full.js              1.11   337.5±24.09ms     2.7 MB/sec    1.00   303.4±14.37ms     3.0 MB/sec
formatter/three.min.js                   1.04    152.5±3.59ms     3.9 MB/sec    1.00    146.1±2.81ms     4.0 MB/sec
formatter/typescript.js                  1.02  1008.4±46.15ms     9.4 MB/sec    1.00   987.6±59.44ms     9.6 MB/sec
formatter/vue.global.prod.js             1.01     51.1±1.50ms     2.4 MB/sec    1.00     50.6±0.59ms     2.4 MB/sec

@netlify
Copy link

netlify bot commented Sep 5, 2022

Deploy Preview for rometools canceled.

Name Link
🔨 Latest commit bd00bde
🔍 Latest deploy log https://app.netlify.com/sites/rometools/deploys/6316e59e5586ff000925e326

@MichaReiser MichaReiser temporarily deployed to netlify-playground September 5, 2022 09:22 Inactive
@MichaReiser
Copy link
Contributor Author

!bench_formatter

@MichaReiser MichaReiser temporarily deployed to netlify-playground September 5, 2022 09:23 Inactive
@MichaReiser MichaReiser added this to the 0.10.0 milestone Sep 5, 2022
@MichaReiser MichaReiser added the A-Formatter Area: formatter label Sep 5, 2022
@github-actions
Copy link

github-actions bot commented Sep 5, 2022

@MichaReiser MichaReiser temporarily deployed to netlify-playground September 5, 2022 12:04 Inactive
@MichaReiser MichaReiser changed the title perf(rome_formatter): Reduce use of PreambleBuffer perf(rome_formatter): Add Buffer.elements Sep 5, 2022
@MichaReiser MichaReiser force-pushed the perf/reduce-preamble-use branch from fd6e822 to 2b24bec Compare September 5, 2022 12:17
@MichaReiser MichaReiser temporarily deployed to netlify-playground September 5, 2022 12:17 Inactive
@rome rome deleted a comment from github-actions bot Sep 5, 2022
@@ -34,8 +34,7 @@ const m2 = module/* B1 */{
export { foo }; /*A5*/
/*A6*/
-}; /*A7*/ /*A8*/
+} /*A7*/
+/*A8*/
+} /*A7*/ /*A8*/
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll have a look at the comments as part of the comments refactoring (which is why this PR shouldn't be part of the 0.9 release)

@MichaReiser MichaReiser force-pushed the perf/reduce-preamble-use branch from 2b24bec to 0613914 Compare September 5, 2022 12:28
@MichaReiser MichaReiser temporarily deployed to netlify-playground September 5, 2022 12:28 Inactive
@MichaReiser MichaReiser temporarily deployed to netlify-playground September 5, 2022 12:35 Inactive
@MichaReiser
Copy link
Contributor Author

!bench_formatter

@MichaReiser MichaReiser marked this pull request as ready for review September 5, 2022 12:36
@github-actions
Copy link

github-actions bot commented Sep 5, 2022

Formatter Benchmark Results

group                                    main                                   pr
-----                                    ----                                   --
formatter/checker.ts                     1.00    532.1±9.61ms     4.9 MB/sec    1.00    530.4±7.74ms     4.9 MB/sec
formatter/compiler.js                    1.00    303.2±7.00ms     3.5 MB/sec    1.02    307.9±4.01ms     3.4 MB/sec
formatter/d3.min.js                      1.00    250.9±5.44ms  1069.7 KB/sec    1.04    261.5±3.49ms  1026.5 KB/sec
formatter/dojo.js                        1.00     15.5±0.35ms     4.4 MB/sec    1.03     16.0±0.10ms     4.3 MB/sec
formatter/ios.d.ts                       1.04    334.3±2.73ms     5.6 MB/sec    1.00    321.1±2.35ms     5.8 MB/sec
formatter/jquery.min.js                  1.00     65.9±2.16ms  1284.4 KB/sec    1.00     66.2±1.39ms  1278.0 KB/sec
formatter/math.js                        1.03    521.3±7.35ms  1271.9 KB/sec    1.00    508.4±4.83ms  1304.2 KB/sec
formatter/parser.ts                      1.00     10.5±0.23ms     4.7 MB/sec    1.00     10.5±0.04ms     4.7 MB/sec
formatter/pixi.min.js                    1.00    284.5±5.69ms  1579.5 KB/sec    1.03    293.7±2.47ms  1530.2 KB/sec
formatter/react-dom.production.min.js    1.00     81.8±2.24ms  1440.4 KB/sec    1.03     84.4±1.72ms  1396.9 KB/sec
formatter/react.production.min.js        1.00      3.9±0.09ms  1617.8 KB/sec    1.02      4.0±0.06ms  1593.0 KB/sec
formatter/router.ts                      1.00      8.2±0.19ms     7.4 MB/sec    1.00      8.2±0.04ms     7.4 MB/sec
formatter/tex-chtml-full.js              1.01   651.2±13.95ms  1432.9 KB/sec    1.00    644.7±6.56ms  1447.4 KB/sec
formatter/three.min.js                   1.00    323.4±7.78ms  1859.2 KB/sec    1.01    325.3±3.34ms  1848.0 KB/sec
formatter/typescript.js                  1.04       2.1±0.01s     4.6 MB/sec    1.00   1980.7±8.62ms     4.8 MB/sec
formatter/vue.global.prod.js             1.00    106.4±3.03ms  1159.4 KB/sec    1.03    109.5±2.64ms  1126.9 KB/sec

@MichaReiser MichaReiser temporarily deployed to netlify-playground September 5, 2022 13:14 Inactive
@MichaReiser MichaReiser temporarily deployed to netlify-playground September 5, 2022 13:16 Inactive
@calibre-analytics
Copy link

calibre-analytics bot commented Sep 5, 2022

Comparing perf(rome_formatter): Add Buffer.elements Snapshot #4 to 1 day median of rome.tools.

LCP? CLS? TBT?
Overall
Median across all pages and test profiles
1.96s
from 561ms
0.0
no change
165ms
from 44ms
Chrome Desktop
Chrome Desktop • Cable
1.96s
from 561ms
0.0
no change
376ms
from 227ms
iPhone, 4G LTE
iPhone 12 • 4G LTE
986ms
from 205ms
0.0
no change
0ms
no change
Motorola Moto G Power, 3G connection
Motorola Moto G Power • Regular 3G
12.3s
from 574ms
0.0
no change
165ms
from 44ms

1 page tested

 Home

Browser previews

Chrome Desktop iPhone, 4G LTE Motorola Moto G Power, 3G connection
Chrome Desktop iPhone, 4G LTE Motorola Moto G Power, 3G connection

Most significant changes

Value Budget
JS Parse & Compile
iPhone, 4G LTE
431ms
from 7ms
JS Parse & Compile
Motorola Moto G Power, 3G connection
1.44s
from 27ms
Total JavaScript Size in Bytes
Chrome Desktop
4.02 MB
from 86.8 KB
Total JavaScript Size in Bytes
iPhone, 4G LTE
4.02 MB
from 86.8 KB
Total JavaScript Size in Bytes
Motorola Moto G Power, 3G connection
4.02 MB
from 86.8 KB

28 other significant changes: JS Parse & Compile on Chrome Desktop, First Contentful Paint on Motorola Moto G Power, 3G connection, Largest Contentful Paint on Motorola Moto G Power, 3G connection, Speed Index on Motorola Moto G Power, 3G connection, Total Page Size in Bytes on Chrome Desktop, Total Page Size in Bytes on iPhone, 4G LTE, Total Page Size in Bytes on Motorola Moto G Power, 3G connection, First Contentful Paint on Chrome Desktop, Number of Requests on Motorola Moto G Power, 3G connection, Time to Interactive on Motorola Moto G Power, 3G connection, Number of Requests on Chrome Desktop, Number of Requests on iPhone, 4G LTE, First Contentful Paint on iPhone, 4G LTE, Time to Interactive on Chrome Desktop, Speed Index on Chrome Desktop, Largest Contentful Paint on iPhone, 4G LTE, Speed Index on iPhone, 4G LTE, Time to Interactive on iPhone, 4G LTE, Total Blocking Time on Motorola Moto G Power, 3G connection, Largest Contentful Paint on Chrome Desktop, Total Image Size in Bytes on Chrome Desktop, Total Image Size in Bytes on iPhone, 4G LTE, Total Image Size in Bytes on Motorola Moto G Power, 3G connection, Total HTML Size in Bytes on Chrome Desktop, Total HTML Size in Bytes on iPhone, 4G LTE, Total HTML Size in Bytes on Motorola Moto G Power, 3G connection, Lighthouse Performance Score on Motorola Moto G Power, 3G connection, Lighthouse Performance Score on Chrome Desktop

Calibre: Site dashboard | View this PR | Edit settings | View documentation

crates/rome_js_formatter/src/jsx/tag/element.rs Outdated Show resolved Hide resolved
Comment on lines +14 to +21
match module_item {
JsAnyModuleItem::JsAnyStatement(JsAnyStatement::JsEmptyStatement(empty)) => {
join.entry_no_separator(&empty.format());
}
_ => {
join.entry(module_item.syntax(), &format_or_verbatim(&module_item));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

What did we change/fix here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't want to insert empty lines if it is an empty statement because that will get removed. This was previously automatically handled inside of entry but turns out to be rather expensive. So it's better to explicitly handle it where it is needed.

crates/rome_formatter/src/builders.rs Outdated Show resolved Hide resolved
@MichaReiser MichaReiser requested a review from leops September 5, 2022 14:43
@MichaReiser MichaReiser temporarily deployed to netlify-playground September 6, 2022 06:16 Inactive
inner: BufferSnapshot,
has_label: bool,
}
pub fn stop(self) -> Recorded<'buf> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we return a newtype here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No very specific reason. Felt more complete if stop returns a Recorded than a slice directly. The new type allows us to implement additional helpers in the future

@MichaReiser MichaReiser merged commit 02e6cdd into main Sep 6, 2022
@MichaReiser MichaReiser deleted the perf/reduce-preamble-use branch September 6, 2022 16:45
ematipico pushed a commit that referenced this pull request Sep 7, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A-Formatter Area: formatter
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants