Skip to content

improve proxy_mode (e.g. rustc wrapper) startup time? #2626

Open

Description

@matthiaskrgr

time ~/.rustup/toolchains/master/bin/rustc >& /dev/null
0,040 total

time ~/.cargo/bin/rustc >& /dev/null
0,272 total

That's almost 7x slower.
I wonder if there is some something we can do to get these numbers a bit closer?

Activity

  1. jyn514 commented on Jan 1, 2021

    @jyn514
    Member

    @matthiaskrgr this is about the rustup wrapper, not the x.py rustc shim, right? If so I think this should be moved to https://github.com/rust-lang/rustup/.

  2. matthiaskrgr commented on Jan 1, 2021

    @matthiaskrgr
    MemberAuthor

    Ah, ~/.cargo/bin/rustc is rustup? Yes, the issue should be moved then. :)

  3. transferred this issue fromrust-lang/ruston Jan 1, 2021
  4. kinnison commented on Jan 1, 2021

    @kinnison
    Contributor

    I can confirm that the difference in performance is around 6x:

    listless% hyperfine ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rustc rustc 
    Benchmark #1: /home/dsilvers/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rustc
      Time (mean ± σ):      11.3 ms ±   0.9 ms    [User: 6.9 ms, System: 4.3 ms]
      Range (min … max):    10.1 ms …  15.4 ms    252 runs
     
    Benchmark #2: rustc
      Time (mean ± σ):      71.2 ms ±   2.3 ms    [User: 60.0 ms, System: 11.2 ms]
      Range (min … max):    67.3 ms …  76.2 ms    41 runs
     
    Summary
      '/home/dsilvers/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rustc' ran
        6.31 ± 0.54 times faster than 'rustc'
    listless%                                                                                
    
    
  5. kinnison commented on Jan 1, 2021

    @kinnison
    Contributor

    Looking locally, ca. 15ms are before main() is entered (presumably due to dynamic link time for all the libraries rustup uses by dint of being linked to curl).

    It looks like about 25ms is taken parsing manifest files, sadly this is also done twice, and thus accounts for the vast majority of the extra time. The toml parser must be quite inefficient.

    It might be possible to knock one of those parse steps on the head since in theory we only need to parse once, but it'll involve quite a bit of refactoring.

    If someone wants to begin to look at this, then the place to start is the config.rs file, around line 620 which is find_or_install_override_toolchain_or_default(). In the sub-function components_exist() you can see it acquire the manifest (since it needs it) and then it lists the components in order to check that everything needed is installed. The list_components() function though ends up loading the manifest itself. Perhaps the distributable toolchain could cache the manifest having loaded it?

  6. rbtcollins commented on Jan 1, 2021

    @rbtcollins
    Contributor

    To help triage this, how much does this contribute to typical build times? like, - sure we can put a chunk of time into making this snappy, and that would be good, but if a typical build is 1500ms ... ?

  7. jyn514 commented on Jan 1, 2021

    @jyn514
    Member

    @rbtcollins I wouldn't notice so much on builds, but this hurts things like rustc --explain E0111 or rustc --help.

    $ time /home/joshua/.local/lib/rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rustc --help >/dev/null
    real	0m0.050s
    user	0m0.026s
    sys	0m0.020s
    $ time rustc --help >/dev/null
    real	0m0.157s
    user	0m0.129s
    sys	0m0.028s
    $ time pip --help >/dev/null
    real	0m0.206s
    user	0m0.178s
    sys	0m0.028s
    $ time go help >/dev/null
    real	0m0.022s
    user	0m0.008s
    sys	0m0.017s
    
  8. matthiaskrgr commented on Jan 1, 2021

    @matthiaskrgr
    MemberAuthor

    to addd to the list:

    time clang >&/dev/null 
    clang >&/dev/null  0,04s user 0,03s system 96% cpu 0,064 total
    
    time gcc >& /dev/null
    gcc >&/dev/null  0,00s user 0,01s system 86% cpu 0,009 total
    
    
  9. kinnison commented on Jan 2, 2021

    @kinnison
    Contributor

    @matthiaskrgr

    Comparing with C compilers is disingenuous since the number of rustc invocations for a similarly sized codebase will be many fewer. For example, rustup is around 130 .rs files, but including its dependencies the total rustc invocations is only 320 or so. If we assume a wasted 35ms (the repeated manifest parse) of CPU time on my computer per rustc invocation and we generously assume that all that waste is reflected in the wallclock compile time at the same parallelism that the build occurs at, then of the 83s it took my computer to build rustup just now at 6.1x parallel, then that's 11375ms wasted which at 6.1x parallel is 1.864s of wallclock time or 2.2% of all build time in a debug build.

    The same maths on a release build, 11375ms wasted for a total build time of 195s is only 0.95% of the total runtime of the build.

    @jyn514

    Considering the rustc --explain or rustc --help scenarios is also disingenuous. Most humans don't need to run those repeatedly in tight loops, and will take far longer than the wasted 50ms or so even noticing the screen updated let alone reading and grokking the output.

    however

    Having said all that, I do think it's worth correcting the double-load of the manifest since it's wasteful to do that anyway, and that shouldn't be too complex to do in a grungy way; or only medium hard to do nicely.

    What would be of even more use though is working out how it takes 35ms on a 4GHz CPU core to parse the manifest, that's utterly abysmal.

  10. kinnison commented on Jan 2, 2021

    @kinnison
    Contributor

    Further investigation yields:

    The majority of the time spent parsing the manifest is in the toml load itself, i.e. before we apply any semantic understanding to the manifest. The channel is around 700k and will only get larger over time. Another possible improvement to do would be to trim out anything not relevant to the toolchain being installed when fetching the manifest. That ought not to be too complex to do and would speed up future operations since the manifest would be significantly smaller

  11. kinnison commented on Jan 2, 2021

    @kinnison
    Contributor

    I wrote a simple trimmer for the manifests to reduce the toml quantity in installed toolchains. Doing that results in the following (stable is untrimmed, beta is trimmed):

    listless% echo stable > rust-toolchain                                        
    listless% hyperfine rustc                                                     
    Benchmark #1: rustc
      Time (mean ± σ):      69.8 ms ±   1.9 ms    [User: 59.5 ms, System: 10.2 ms]
      Range (min … max):    67.4 ms …  76.1 ms    42 runs
     
    listless% echo beta > rust-toolchain                                          
    listless% hyperfine rustc                                                    
    Benchmark #1: rustc
      Time (mean ± σ):      19.6 ms ±   0.7 ms    [User: 14.2 ms, System: 5.4 ms]
      Range (min … max):    18.7 ms …  22.9 ms    128 runs
     
    listless%                                                                    
    

    (for reference, directly invoking rustc (stable or beta) without rustup in the way is ca. 11ms.

  12. kinnison commented on Jan 2, 2021

    @kinnison
    Contributor

    @matthiaskrgr Assuming #2627 goes CI-green, you might want to try the binary from there and see how it affects performance for you.

  13. 30 remaining items

  14. ChrisDenton commented on May 21, 2025

    @ChrisDenton
    Member

    So to ask the obvious first, do we need to load any metadata while in proxy mode? It's just to validate the components are installed right? Which feels more like the job of rustup proper so the proxies can just assume everything works.

  15. rami3l commented on May 21, 2025

    @rami3l
    Member

    So to ask the obvious first, do we need to load any metadata while in proxy mode? It's just to validate the components are installed right? Which feels more like the job of rustup proper so the proxies can just assume everything works.

    @ChrisDenton Wait, IIRC ensure_active_toolchain() was exactly the call that we brought back in v1.28.1 (and later renamed in v1.28.2) to avoid the previous breakage (and the same call that made a whole bunch of query-only functions unnecessarily async), and in that very context, this call should be necessary...

    rustup/src/config.rs

    Lines 534 to 538 in ac249c8

    if !should_ensure {
    return self.active_toolchain();
    }
    match self.ensure_active_toolchain(true, false).await {

    @Kobzol This makes me wonder, what will the graph look like if the new behavior is used instead (RUSTUP_AUTO_INSTALL=0)?

  16. ChrisDenton commented on May 21, 2025

    @ChrisDenton
    Member

    Wait, IIRC ensure_active_toolchain() was exactly the call that we brought back in v1.28.1 (and later renamed in v1.28.2) to avoid the previous breakage, and in the very context of that breakage, this call should be necessary...

    Ah right, that's a pain. So yeah persuading people to disable auto-install is the best call. 😉

    My next thought would be to encode the essential information in a super simple binary format that's fast to load into memory.

  17. bjorn3 commented on May 21, 2025

    @bjorn3
    Member

    Can we encode just the list of installed components in a binary file and only validate this file when running in proxy mode? And then leave any consistency checks that need to parse the full manifest to directly running rustup or when the proxy detects that a component is missing.

  18. Kobzol commented on May 21, 2025

    @Kobzol
    Member

    So to ask the obvious first, do we need to load any metadata while in proxy mode? It's just to validate the components are installed right? Which feels more like the job of rustup proper so the proxies can just assume everything works.

    @ChrisDenton Wait, IIRC ensure_active_toolchain() was exactly the call that we brought back in v1.28.1 (and later renamed in v1.28.2) to avoid the previous breakage (and the same call that made a whole bunch of query-only functions unnecessarily async), and in that very context, this call should be necessary...

    rustup/src/config.rs

    Lines 534 to 538 in ac249c8

    if !should_ensure {
    return self.active_toolchain();
    }

    match self.ensure_active_toolchain(true, false).await {
    @Kobzol This makes me wonder, what will the graph look like if the new behavior is used instead (RUSTUP_AUTO_INSTALL=0)?

    With that env flag, rustc takes ~22ms, instead of ~48ms, so the manifest loading seems to be removed in that case.

  19. rami3l commented on May 21, 2025

    @rami3l
    Member

    @Kobzol Thanks! So setting RUSTUP_AUTO_INSTALL=0 gives a reasonable workaround then.

    It is also worth mentioning that with auto installation enabled, rustc --version can take up to minutes to complete (that is, when the active toolchain is missing), so it's absolutely bad news for anyone who wish this command could run faster.

  20. ChrisDenton commented on May 21, 2025

    @ChrisDenton
    Member

    Or rustup set auto-install disable for people who prefer not to use environment variables.

  21. Kobzol commented on May 21, 2025

    @Kobzol
    Member

    Are there plans to flip the default in the future, or is it expected that the auto-install behavior will stay the default? If it is the latter, I think that it could still be worth it to optimize the manifest loading.

  22. ChrisDenton commented on May 21, 2025

    @ChrisDenton
    Member

    I personally would like to move towards it being the default but we tried that in 1.28.0 and it was not well received. So it'll likely we'll support at least some (perhaps more limited) form of auto-install for awhile yet.

    So, speaking for myself, I think it may be worth optimising the happy path if someone wants to work on that. But other team members may have other opinions.

  23. djc commented on May 21, 2025

    @djc
    Contributor

    Are there plans to flip the default in the future, or is it expected that the auto-install behavior will stay the default? If it is the latter, I think that it could still be worth it to optimize the manifest loading.

    I would say there are plans to flip the default, but I think none of us are currently very motivated to push the issue forward.

  24. Kobzol commented on May 22, 2025

    @Kobzol
    Member

    I tried to switch the loading to JSON, and with that running ./rustup which rustc takes ~20ms on my machine (vs ~40ms previously), out of which a large portion is building the regex for tracing-subscriber, which should be fixed with tokio-rs/tracing#3243 (to be backported in tokio-rs/tracing#3283). I noticed that there was already an attempt to optimize this in #2627, but it wasn't merged, so not sure how this will work out, but anyway - here's the PR: #4344.

  25. Kobzol commented on May 27, 2025

    @Kobzol
    Member

    #4350 should completely skip manifest parsing in most cases (unless there is a rust-toolchain.toml file). That reduces the overhead considerably, and with tokio-rs/tracing#3243 I think that it will be practically negligible for most rustup proxy invocations.

  26. Kobzol commented on Sep 3, 2025

    @Kobzol
    Member

    #4405 should improve the speed of loading TOML files, even if you have a rust-toolchain.toml file.

  27. changed the title [-]improve rustc wrapper startup time?[/-] [+]improve `proxy_mode` (e.g. rustc wrapper) startup time?[/+] on May 21, 2026
  28. Kobzol commented on Jun 15, 2026

    @Kobzol
    Member

    Performance on my PC (64-bit Linux) with Rustup 1.29.0:

    hyperfine "~/.cargo/bin/rustc" "~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rustc"
    Benchmark 1: ~/.cargo/bin/rustc
      Time (mean ± σ):      15.5 ms ±   0.5 ms    [User: 4.8 ms, System: 11.6 ms]
      Range (min … max):    14.2 ms …  17.3 ms    181 runs
     
    Benchmark 2: ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rustc
      Time (mean ± σ):       9.8 ms ±   0.3 ms    [User: 2.6 ms, System: 7.5 ms]
      Range (min … max):     8.7 ms …  10.9 ms    281 runs
     
    Summary
      ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rustc ran
        1.58 ± 0.07 times faster than ~/.cargo/bin/rustc
    

    So much better than the original number when this issue was created.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions