rust-lang / rustfmt

Format Rust code
https://rust-lang.github.io/rustfmt/
Apache License 2.0
6.04k stars 888 forks source link

High overhead #6353

Open TomFryersMidsummer opened 1 month ago

TomFryersMidsummer commented 1 month ago

Rustfmt has quite high per-run overhead. For example, running repeated benchmarks on my machine gives the following means:

Here, lib.rs is rustfmt’s own 670-line file. 55 ms for 670 lines is pretty good! But 48 ms to format the empty string doesn’t seem quite so speedy.

Some other formatters have a much lower overhead: ruff format -, for instance, takes 4 ms. Although some are far worse: echo | prettier --parser babel takes 230 ms.

This can make quite a difference when using rustfmt in scripts. (I have a script that formats proptest function bodies, which calls rustfmt 452 times.)

ytmimi commented 1 month ago

@TomFryersMidsummer thanks fort the report. What version of rustfmt are you using? How are you calling rustfmt in your scripts? Do you have a rustfmt.toml file in the project? have you profiled rustfmt to determine where it's spending the most time?

Also, running rustfmt on lib.rs is going to format the entire project, not just the lib.rs file. You can see this if you add the -v flag to your call e.g. rustfmt -v src/lib.rs. If you're using nightly rustfmt you can pass along the --unstable-features --skip-children flags to only format the current file.

TomFryersMidsummer commented 1 month ago

I'm using rustfmt 1.8.0-nightly (7608018cbd 2024-09-29). I have rustfmt.toml (edition = "2021"), but running outside the project directory makes no difference.

I’m passing source code to rustfmt on standard input, but the problem is the same if I put it in a file instead. The issue arises even with no input: rustfmt --help.

I haven’t profiled Rustfmt, but I have noticed that it’s nearly twice as fast if I specify the toolchain manually, with +nightly or +stable, so perhaps toolchain selection is taking a very long time.

TomFryersMidsummer commented 1 month ago

Better still: echo | ~/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustfmt is only 10 ms.

TomFryersMidsummer commented 1 month ago

Largely, it looks like this is caused by this Rustup issue. But it would still be nice to be down closer to Ruff when calling the binary without the wrapper.

ytmimi commented 1 month ago

That's all very helpful info!

calebcartwright commented 1 month ago

I’m passing source code to rustfmt on standard input, but the problem is the same if I put it in a file instead

are you making those invocations sequentially or in parallel? in the latter, are you invoking rustfmt multiple times and passing it one file each time or invoking rustfmt once and passing the full list of file paths?

I'd imagine there's some level of bootstrapping overhead each time that establishes a runtime floor, but unless there's some really straightforward low hanging fruit I don't think this is something we can really do much about, or at least I don't think we've got the spare capacity to try to optimize away a couple dozen milliseconds

TomFryersMidsummer commented 1 month ago

are you making those invocations sequentially or in parallel?

In parallel.

in the latter, are you invoking rustfmt multiple times and passing it one file each time or invoking rustfmt once and passing the full list of file paths?

I'm passing it one file each time on standard input. I couldn't find a way to get Rustfmt to take more than one file on standard input. I could save everything to temporary files on tmpfs, pass this list to Rustfmt, then read them all back, but that's a bit of a silly workaround, with its own overheads. I could also concatenate them, with some special comment as a separator, and try to split them back up, but that doesn't work if a file contains syntax errors.