-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Performance of async printers (was: Adopt swc
parser?)
#13158
Comments
A collaboration with dprint might make sense for this, since it already uses swc's parser and is written in rust. Could be a prettier mode within dprint or something. |
FYI previously I tried to use swc(wasm) parser for Prettier but it could not improve perf |
How did that look? Was swc parsing, and then somehow a JS AST was being generated from that? I assume pretty slow to convert the rust structs to JS objects. |
Prettier spend much more time in |
Do you have any numbers to share? |
Sorry it was in my laptop..., and I've switched laptop so I lost the branch |
Do you have information about #13158 (comment)? Was the rust AST converted to JS objects? |
Maybe there are several overhead in SWC ESTree parser.
I think SWC wasm parser cannot improve perf for us for above reasons |
Right, so for this idea to be viable, prettier's printer needs to be ported to rust. |
I'm no expert, but I see lots of printer logic does stuff like this: |
Another performance-related question: was performance measured before and after for #13104? |
Yes, I did, there are no noticeable differences. |
My benchmarking shows a 10-20% drop in performance due to #13104. Is there a difference between our testing methodologies?
benchmark.sh#!/usr/bin/env bash
doSetup=0
if [ doSetup = 1 ]
then
git worktree add before
git worktree add after
(
cd before
git checkout 8dae4abf41e1ccec3515e8acea159505bbd55f33~1
yarn
yarn build
)
(
cd after
git checkout 8dae4abf41e1ccec3515e8acea159505bbd55f33
yarn
yarn build
)
fi
#https://github.com/sharkdp/hyperfine
hyperfine 'node ./bench.mjs before 1000' 'node ./bench.mjs after 1000' bench.mjsconst [, , version, groupCountString, groupSizeString, method] = process.argv;
const groupCount = +groupCountString;
const groupSize = +groupSizeString;
const prettierApi = await import(`./${version}/index.js`);
import {readFileSync} from 'fs';
const printResults = false;
const sourceText = readFileSync('./before/src/main/comments.js', 'utf8');
for(let i = 0; i < groupCount; i++) {
if(method === 'serial') {
for(let i = 0; i < groupSize; i++) {
const result = await prettierApi.format(sourceText, {
parser: 'typescript'
});
if(printResults) console.log(result);
}
}
if(method === 'parallel') {
const promises = [];
for(let i = 0; i < groupSize; i++) {
promises.push(prettierApi.format(sourceText, {
parser: 'typescript'
}));
}
await Promise.allSettled(promises);
}
} |
I think it's hard to just migrate a part of the logic to rust or wasm to improve speed. |
https://github.com/prettier/prettier/blob/next/CONTRIBUTING.md#performance I ran this script on both |
@cspotcode To test the built version, you need to change this: -const prettierApi = await import(`./${version}/index.js`);
+const prettierApi = await import(`./${version}/dist/index.js`); |
Even after updating my benchmark to use the built version, I get similar results. But I'll try the scripts in
|
I still get a big difference between 6c722c7990f4e8b03b1ed6c01fbbe749eaed0224 and 8dae4abf41e1ccec3515e8acea159505bbd55f33 52.3 ops/sec vs 37.5 ops/sec "After" runs at 71% the speed of "before."
|
Thanks for testing, maybe I did something wrong, I'll verify again later. But the asynchronous parser support is requested for parsers, and I can't figure a way to do async parse and synchronous print, so maybe we'll have to accept that. |
Usually, if function A calls function B, and if B is async, then A must be async, too. But the opposite is not true: if function A is async, function B can remain sync. By that logic, is the problem that the printer must call the parser? Shouldn't parse happen first, and then print? |
You are right, the problem is we support language in another language eg: css/js in html, so we'll call parse during print. I also thought about parse them before print, in that way we don't need async printer, but problem is we don't even know how to traverse AST, currently the AST print it's children itself. I thought about this for a long long time, before I made the move. Can't see a better solution. |
I see, thanks for the explanation. As I understand it, embed does two things:
I understand that step 2. must be async. But do any plugins require step 1. to be async? If step 1. can remain sync, then maybe step 2. can be deferred. The Once sync The majority of cases are not going to embed. It seems an awful shame to reduce performance by 20% or 30%, rather than tweaking the embedding API to preserve that performance. |
Unfortunately they do, sometimes we need make decisions based on printed docs. Also the embed is allowed to fail, then we need call print as fallback, but maybe this can solve, we can wait another round. |
I don't like that either, I'll be glad if someone can come up a solution. |
Maybe something similar can be done to what React does with throwing promises. Something like this in while (!doc) {
try {
doc = mainPrint();
} catch (caught) {
if (caught instanceof AsyncWorkBailout) {
cache[caught.node] = await caught.promise;
path.resetToRoot();
} else {
throw caught;
}
}
} Docs for individual nodes are cached (not all of them, but that can be improved), so restarting the printer might be cheap. Still looks like a lot of overhead. |
Interesting.
Agree. |
This seems to be a better option. Traversing can be implemented in a generic way like its done when attaching comments. |
I tried this approach (see thorn0@89f1ad2). The results are even worse:
I might have missed some low-hanging-fruit optimization, but this async AST traversing seems to be really expensive. |
Maybe we can still use sync traverse, and add promises to |
IDK how to implement that properly. Consider this input: function HelloWorld() {
return html`
<h3>Bar List</h3>
${bars.map(
(bar) => html`
<p >${bar}</p>
`
)}
`;
} When |
I guess we'll have to pass a different async |
Seams to work, didn't benchmark it yet: thorn0@6749a2f |
Not bad:
It can be optimized by splitting |
That's great! |
Results for the mentioned optimization (thorn0@41e905d):
I wonder what's going with the "parallel" results. |
Seems not much difference than #13158 (comment) |
Reused
|
Okay, I think it's good enough. thorn0@77ca54c
|
@fisker @sosukesuzuki If you're okay with this solution (diff), how do we proceed? IMHO it doesn't make sense to open a PR against |
I'll try to revert it. |
Great. I tried and gave up. |
Meta thought: does it make sense to rename this issue? If we do that, it will be more intuitive when it appears in notifications. |
swc
parser?swc
parser?)
@sosukesuzuki Yes, let's close it. If someone wants to discuss Rust, please open a new issue. |
I don't know how faster can it be.
The parser is not the really problem slowing us down, but should be faster.
Hope we can rewrite our print in Rust? Maybe?
The text was updated successfully, but these errors were encountered: