rust-lang/rustfmt

High overhead

TomFryersMidsummer opened this issue · 7 comments

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

  • echo | rustfmt: 48 ms
  • cat lib.rs | rustfmt: 55 ms

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.)

@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.

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.

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

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.

That's all very helpful info!

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