Skip to content
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

Why does Rust Analyzer use so much RAM and CPU? #11325

Closed
mankinskin opened this issue Jan 20, 2022 · 20 comments
Closed

Why does Rust Analyzer use so much RAM and CPU? #11325

mankinskin opened this issue Jan 20, 2022 · 20 comments

Comments

@mankinskin
Copy link

mankinskin commented Jan 20, 2022

It seem to me that RA basically builds the project and maintains a collection of build files in live memory, at least that is how I explain its high memory usage.

After opening a rust project with vs code and RA extension the rust-analyzer process takes 1GB RAM upwards and CPU is constantly at around 20%, which seems like a lot if it isn't doing anything. It makes it unpleasant to have the editor running in the background for example.

Are there any reasons for this? Can this be fixed? I feel like this has been a major issue with RA for a long time and it hasn't really been improving despite a lot of updates.

I wonder why RA can't just look for existing source files and dependencies etc, check if they are up to date, if not lock the build directory and update, then use the on-disk build artifacts it needs, does any indexing etc. to show types and auto completion, caches that on disk to reuse for next session, updates the index if files change and then only calls cargo check to get errors and warnings, updates the index only when files change, etc. I don't know where 1GB of RAM would be needed here while idling, let alone using that much CPU. The index doesn't seem like it should be that large.

Sorry, I don't mean to sound off putting, and I know this is open source software, but I feel like these issues are not getting enough attention or explanation as to why they can't be fixed. If it is a developer resources issue it would certainly help if we can document where performance issues might stem from.

@lnicola
Copy link
Member

lnicola commented Jan 20, 2022

There's a lot to unpack here, but does it really use CPU on your system while you're not typing?

@bjorn3
Copy link
Member

bjorn3 commented Jan 20, 2022

I'm not sure why it uses the cpu when doing nothing, but the high memory usage is because it caches information to avoid having to recompute it after every keystroke which would be very much noticable. By the way are you on windows? If so is it completely using 100% of a single core (which could be ~20% of all cores depending on the amount of cores) or 20% of a single core? Task manager reports the percentage of all cores that is used, unlike linux and macos which report the percentage of a single core with full usage of two cores resulting in 200%. If it is 100% of a single core, it may be a hang in for example chalk (this happened in the past).

Is this https://github.com/mankinskin/graph_app? I just tried rust-analyzer analysis-stats on it. The memory usage roughly matches what I got. It doesn't seem to hang. It did take about a minute to run though. (including 20s to download dependencies from crates.io I think) I also tried opening it in vscode. This again took a while to load, but after it was loaded it didn't use the cpu at all when not doing anything.

@mankinskin
Copy link
Author

mankinskin commented Jan 20, 2022

I am on Linux right now, and this was with a small local project, but the dependencies are actually kind of similar to graph_app (also uses egui).

It seems to have calmed down for some reason, when I first opened the project it seemed like it was still working minutes after it had finished showing status messages. I have firefox open alongside, which also takes a lot of RAM for some reason, so that might have added to it.

Here this was my CPU usage history while having VS Code with rust-analyzer open in the background, while only doing things in firefox really
cpu_usage

Then I closed vscode (first mark) and proceeded relatively normally with firefox and reopened vscode later:
cpu_usage2

Of course there is some impact to be expected and it isn't as stark (20%) as I described all of the time apparently. That was after I first opened this project, rust-analyzer showed a loading icon in the status bar for around a minute, but also I instantly ran cargo run in the terminal, so maybe it built the project twice? (sometimes it shows waiting for a file lock on build directory, but not this time)

I think the main reason this is causing problems for me is because I can't control when rust-analyzer does these heavy computations. Right after I start vs code it automatically basically indexes the whole project and might download a lot of dependencies. I haven't found an option to disable it but I think this would really make it more manageable, basically don't check on startup and have a button in the status bar to update rust-analyzer.

But also I wonder why it takes so long in the first place, or why it uses that much memory? Is the index really that huge? Does rust-analyzer rebuild the project? I imagine it just gives completions and runs cargo check to retrieve errors and warnings including their locations. It often feels like it does a lot of unnecessary work because my project doesn't even reference 90% of the code it seems to be indexing directly.

@lnicola
Copy link
Member

lnicola commented Jan 20, 2022

Of course there is some impact to be expected and it isn't as stark (20%) as I described all of the time apparently.

That also includes the CPU usage of VS Code itself, which isn't trivial. On my system, Electron apps still use some CPU.

Right after I start vs code it automatically basically indexes the whole project

You can disable that with "rust-analyzer.cache.warmup": false. Some later requests will be slower this way, but there won't be any "indexing" step spinning up your CPU fans.

and might download a lot of dependencies.

Because it runs cargo metadata and cargo build. The first one is necessary in order to obtain the project information (we actually need to look at the source code of the dependencies), the second one can be disabled (but then you won't get rustc diagnostics).

Is the index really that huge?

With cache warm-up disabled, the starting memory usage on the RA repo itself is 154 MB. It goes up to 605 MB with cache warm-up enabled. From what I've seen, it doesn't go much past 1 GB, and this is on a pretty large codebase, 226 KLOC without dependencies, according to tokei.

And there's a nifty Memory usage command:

Per-query memory usage:
    90mb FileItemTreeQuery
    58mb CrateDefMapQueryQuery
    45mb FileTextQuery
    36mb HygieneFrameQuery
    34mb MacroArgTextQuery
    27mb InternMacroCallQuery
    22mb ParseQuery
    22mb AttrsQuery
    18mb ImplDataQuery
    17mb AstIdMapQuery
    13mb MacroDefQuery
    12mb GenericParamsQuery
    12mb ImplTraitQuery
    10mb InternFunctionQuery
    10mb ImportMapQuery
    10mb ImplSelfTyQuery
     6mb ParseMacroExpansionQuery
     6mb MacroExpandQuery
     5mb InternImplQuery

For example, FileItemTreeQuery is a tree of every item defined in every file in the project. You can view a dump of it for the current file by running the Debug ItemTree command. CrateDefMapQuery has pretty much everything in the code, from proc macros to a list of items visible in each scope. That cache warm-up option? It just precomputes these two. FileTextQuery is the contents of every file. There are macro call arguments, macro expansion results and so on.

Some of these queries could go away, at the cost of performance. We don't want to recompute the types and inlay hints, or to re-expand the macros in a file just because you've added a line at the top. A batch compiler like rustc doesn't care about this.

Of course, the memory usage could still be improved, but there's no magic optimization that would cut it in half.


And having said that, how much memory would you expect it to use on a 226 KLOC project? How much does the compiler use? Or IntelliJ on a similarly-sized project? Or clangd, or Visual Studio.

@bjorn3
Copy link
Member

bjorn3 commented Jan 20, 2022

That was after I first opened this project, rust-analyzer showed a loading icon in the status bar for around a minute, but also I instantly ran cargo run in the terminal, so maybe it built the project twice? (sometimes it shows waiting for a file lock on build directory, but not this time)

Rust-analyzer uses cargo check for most showing errors. (it also has a couple of builtin errors) cargo check and cargo build don't share most build artifacts. cargo check only produces crate metadata, while cargo build also compiles object files (and for programs an executable)

But also I wonder why it takes so long in the first place, or why it uses that much memory? Is the index really that huge? Does rust-analyzer rebuild the project?

Apart from the cargo check run I already mentioned it runs it's own analysis. The results from cargo check can only be used for error messages. The own analysis is used for all other ide functionalities, including but not limited to showing syntax errors, completions and inlay type hints (these show the type of variables in your code even if you didn't explicitly write it). Rust-analyzer does this for all crates and keeps the results in memory for all of them at once. Rustc only needs to keep the analysis results for the current crate in memory and uses memory maps to load the subset of analysis results of other crates that are necessary for analysis of the current crate from the compiled rlibs. Parts of memory maps that aren't yet used by the current process don't contribute to the resident set size, rust-analyzer has to keep it in memory if any crate uses it. Rust-analyzer doesn't write anything to the disk, so it can't use memory maps. Besides using memory maps with temporary files is worse than using swap as it is guaranteed to hit the disk. There are some optimizations to skip part of the analysis of dependencies outside of the current workspace when not necessary, but a lot is still necessary.

@flodiebold
Copy link
Member

It often feels like it does a lot of unnecessary work because my project doesn't even reference 90% of the code it seems to be indexing directly

In addition to what the others wrote, the way that macros and name resolution in Rust work together means that all macros outside of item bodies in a crate need to be expanded to resolve any names. So while you may indeed not reference 90% of that code, it still needs to be expanded to determine that. And that's mainly what the "indexing" step actually does. (Also, dependencies need to be expanded so they're available for autoimports.)

@mankinskin
Copy link
Author

mankinskin commented Jan 21, 2022

And there's a nifty Memory usage command

How can I run these commands from vs code? I can't seem to find it in the user manual.

For example, FileItemTreeQuery is a tree of every item defined in every file in the project. ... CrateDefMapQuery has pretty much everything in the code, from proc macros to a list of items visible in each scope. ... FileTextQuery is the contents of every file. There are macro call arguments, macro expansion results and so on.

Isn't this a whole lot of redundancy? Seems like we are storing the same things at least 3 times here. That would of course impact loadup times because more memory needs to be allocated and the disk might be read repeatedly.

how much memory would you expect it to use on a 226 KLOC project?

Actually less than the actual code. We just need to store public signatures that are actually accessible from our current crate or even module. Also we could compress repeated identifiers quite easily. Sure the indexing structure also takes some extra space, but 226 KLOC is roughly 2MB-3MB (comparing here to a project of 170KLOC and 1.3MB), so 1GB seems like a very expensive indexing structure. What is all this memory used for?

As a more immediate improvement, more control of when rust analyzer executes jobs would make using it much more pleasant. RA is very "greedy" right now, so even when I don't need it at the moment, it might force an update which may download, index dependencies, rebuild the entire project and so on.

I think there should be an opt-in safety latch in addition to the "check on save" option, which prevents RA from doing more than a quick "cargo check" after saving i.e. it should detect if dependencies need to be fetched, anything needs to be indexed or whether anything needs to be compiled to run cargo check. Cargo check should not take more than 2 seconds on basically all projects, because it should really only check the local crate. But when it can't, it instantly occupies my entire system to fetch and index all dependencies for a relatively long time, which can be really annoying.

For example, I usually build the project from the terminal in vscode. When I open a new project, rust analyzer may not have any index for it yet. When I make one change and save, rust analyzer wants to run check, realizes it needs to index, and I have to wait for a straight minute sometimes before I can proceed. Then, because rust analyzer doesn't share its resources with the current workspace, I still need to run my "own" cargo build in the terminal if I want to run any tests or anything.

Even just when opening a project rust analyzer may randomly decide to index the project even without saving a file. If RA has to make such heavy calls, it would be nice if it were more responsible with calling them.

So using RA basically forces me to build the project twice at the current state. It would be nice to get a message "update metadata?" if dependencies would need to be fetched or the internal index is not quick to build, especially after just opening a project and not doing anything else.

For people who don't care about this, or have beefy machines, fast internet and don't notice the overhead, they can disable asking before doing downloads or indexing, so they get the current behavior. But for people with less system resources like me and also for very large projects this seems to be a good and quick solution.

@mankinskin
Copy link
Author

So while you may indeed not reference 90% of that code, it still needs to be expanded to determine that. And that's mainly what the "indexing" step actually does. (Also, dependencies need to be expanded so they're available for autoimports.)

auto-imports = quick fix feature right? So that makes sense but it still would only need to index the signatures of public items in crates my crate is directly depending on, or crates being re-exported by direct dependencies. This is never in the realms of 1GB on the projects I have been working on.

@mankinskin
Copy link
Author

Besides using memory maps with temporary files is worse than using swap as it is guaranteed to hit the disk. There are some optimizations to skip part of the analysis of dependencies outside of the current workspace when not necessary, but a lot is still necessary.

It may still be useful to store crate indexes on disk to reuse them for the next session instead of rebuilding them every time.

@lnicola
Copy link
Member

lnicola commented Jan 21, 2022

How can I run these commands from vs code? I can't seem to find it in the user manual.

Press F1 and type Memory Usage in the input box that shows up. The full command is called Rust Analyzer: Memory Usage (Clears Database) and it's described in the manual here. Note that because of a bug, you will need to restart the language server after invoking it (there's a Restart server command).

Isn't this a whole lot of redundancy? Seems like we are storing the same things at least 3 times here. That would of course impact loadup times because more memory needs to be allocated and the disk might be read repeatedly.

It's controlled redundancy. The file source code is needed because we don't want to hit the disk e.g. during completion, and for a more subtle reason, too: it might change while we're processing a request.

So that makes sense but it still would only need to index the signatures of public items in crates my crate is directly depending on, or crates being re-exported by direct dependencies. This is never in the realms of 1GB on the projects I have been working on.

That's exactly what we do, and it takes some 10s of MB for the whole project. ImportMapQuery (a part used for that) takes 10 MB in my listing.

Actually less than the actual code.

No. An AST will take more memory than the source code, because it's a tree and it needs to represent relationships between the nodes. a + b * c takes 9 bytes of source code, but in an a classic AST you need 5 nodes, each having at minimum one byte for the type and two pointers (8 bytes each), which, ignoring any padding, adds up to at least 5 * 17 = 85 bytes. That's without storing the identifiers themselves, or their location in the source file. To be able to change that tree efficiently, you also need to keep track of the spaces next to the nodes.

And that's just the AST. We compute much more, as I said: types, macro expansion results and so on. We have to store these in such a way that typing in a file doesn't invalidate everything.

but 226 KLOC is roughly 2MB-3MB (comparing here to a project of 170KLOC and 1.3MB)

226 KLOC without the transitive dependencies, and libstd, libcore and friends.

What is all this memory used for?

The Memory Usage command I mentioned should answer that.

RA is very "greedy" right now, so even when I don't need it at the moment, it might force an update which may download, index dependencies, rebuild the entire project and so on.

I does nothing more than a cargo check. If it's the first time you're opening a project or you've just added a new dependency, if you've upgraded the compiler, or changed RUSTFLAGS, that might download something or rebuild the project. But how could we offer completions for crates you don't have on disk.

I think there should be an opt-in safety latch in addition to the "check on save" option, which prevents RA from doing more than a quick "cargo check" after saving i.e. it should detect if dependencies need to be fetched, anything needs to be indexed or whether anything needs to be compiled to run cargo check. Cargo check should not take more than 2 seconds on basically all projects, because it should really only check the local crate. But when it can't, it instantly occupies my entire system to fetch and index all dependencies for a relatively long time, which can be really annoying.

RA doesn't do anything else besides "a quick cargo check" (it runs cargo metadata, but that's even quicker, you can run it in a terminal and see). If cargo check takes more than two seconds because it needs to rebuild the project, we can't do anything about it.

For example, I usually build the project from the terminal in vscode. When I open a new project, rust analyzer may not have any index for it yet. When I make one change and save, rust analyzer wants to run check, realizes it needs to index, and I have to wait for a straight minute sometimes before I can proceed. Then, because rust analyzer doesn't share its resources with the current workspace, I still need to run my "own" cargo build in the terminal if I want to run any tests or anything.

The "index" is not what you think, and you can disable it using the preference I mentioned above. Or if you don't, you don't need to wait until Indexing disappears from the status bar before using the IDE.

Even just when opening a project rust analyzer may randomly decide to index the project even without saving a file. If RA has to make such heavy calls, it would be nice if it were more responsible with calling them.

That should only happen if you change Cargo.toml or Cargo.lock, for example switching git branches might trigger a reload.

So using RA basically forces me to build the project twice at the current state.

No, it doesn't. If you run cargo check from the terminal, then load rust-analyzer and it rebuilds the project, it's probably a configuration issue (different environment variables, a proc macro or build script that does something weird etc.).

It would be nice to get a message "update metadata?" if dependencies would need to be fetched or the internal index is not quick to build, especially after just opening a project and not doing anything else.

It would be nice to get a message "update metadata?" if dependencies would need to be fetched or the internal index is not quick to build, especially after just opening a project and not doing anything else.

rust-analyzer can't work without the source code of the dependencies. We get this information from cargo metadata (give it a try) and we can't load the project if that fails.

It may still be useful to store crate indexes on disk to reuse them for the next session instead of rebuilding them every time.

Sure, but that's very hard to implement, and there are some good reasons not to: #4712.

@flodiebold
Copy link
Member

So that makes sense but it still would only need to index the signatures of public items in crates my crate is directly depending on, or crates being re-exported by direct dependencies

Determining those requires doing the same for their dependencies, and so on. That's information that could be thrown away afterwards, especially once we have persistent caches, sure, but that's not trivial to manage and we've had other priorities so far.

@flodiebold
Copy link
Member

I don't think there's anything actionable in this ticket (it would really have been better asked in the forums in the first place), so let's close it.

@justingolden21
Copy link

I noticed on my 32gb ram gaming desktop that my PC fan was always top speed. Checked and it's entirely vs code. Expanded and it's rust-analyzer.exe at very high power usage and using nearly all CPU and RAM. I've got a basic tauri app and I'm not sure what it's doing in the background but it really goes brrr

@ArmoredPony
Copy link

ArmoredPony commented Sep 12, 2024

I have this problem with any project with a big number of dependencies (and subdependencies). For example, any game, that uses Bevy game engine, OOM's VS Code quite often

@mankinskin
Copy link
Author

#18165

This is hopefully an actionable issue to mitigate this

@Rudxain

This comment has been minimized.

@lnicola
Copy link
Member

lnicola commented Jan 23, 2025

Of course it's loading std, how would it work otherwise?

@Rudxain

This comment has been minimized.

@lnicola
Copy link
Member

lnicola commented Jan 23, 2025

We already do all of that. We use about 1.2 GB on rust-analyzer itself, which is a rather large codebase and seems pretty reasonable to me. Things might improve in the future, so please follow along.

@Rudxain
Copy link

Rudxain commented Jan 23, 2025

Ok thanks for your time (and the team as a whole, for their work!)

I'll hide my comment as outdated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants