Introducing the ReSharper performance series
With every ReSharper update, we get questions around performance. Some users notice a slightly slower solution load, others see a minor indexing lag, and others get bugged by a yellow notification bar stating “Extension ‘JetBrains ReSharper Ultimate’ likely caused 9 seconds of unresponsiveness. Disabling it may improve your experience.” – and yes, we have seen higher values there, too.
We hear you. We use ReSharper to develop ReSharper. It may not always be visible, but we are working hard on improving ReSharper performance. And because those changes aren’t always visible, we are starting a blog series about the minor and major changes that are in the works.
We’ll touch on some high-level things, but will also open up the hood and see what happens underneath.
In this series:
- Introducing the ReSharper performance series
- Taking ReSharper out of process
- Component composition, just-in-time-compilation, the UI thread
- Performance improvements in ReSharper 2018.1 and 2018.1.1
Note these posts will be long – there’s a lot of context involved. So let’s start at the beginning – a little bit of history!
Visual Studio and ReSharper
Visual Studio services many developers across Windows, the Web and multiple Cloud and Mobile platforms. It builds on years of history, and has gone through a number of changes over the years. New languages and frameworks came about, often with new development paradigms. The C# compiler moved to Roslyn, adding additional code analysis and refactoring features to Visual Studio.
Development workloads are always in motion, and thanks to Visual Studio having a very extensible architecture, it has always been able to follow new development trends and provide a rich IDE experience.
Plugging into that extensible architecture, ReSharper increases Visual Studio value by extending existing features and by providing additional capabilities like additional code analysis rules and quick-fixes, structural search-and-replace, 55 refactorings, improved code generation, build and debugging features, and much, much more – an overview is available in our ReSharper vs. Visual Studio overview.
An additional consideration is that ReSharper doesn’t only extend the latest Visual Studio version. Many of our customers are using previous Visual Studio versions for a variety of reasons, making ReSharper extend Visual Studio 2010 SP1, 2012 (all three updates), 2013 (all five updates), 2015 (all three updates), and all of the updates 2017 continuously brings.
This means ReSharper still knows about
project.json, has knowledge of lightweight solution load which was introduced and then removed, and many more.
The point we are trying to make here is that both products are complex in nature. And there is even more complexity! Have you ever thought about how difficult it is for an IDE to work with incomplete, broken code that does not compile? It is what happens all the time while we are writing lines of code… And that broken code can be refactored! We can navigate around our project! There’s code completion! It’s awesome, really, that Visual Studio and ReSharper can do this!
I, for one, would not want to go back to the basic tools we had in 1998 – having a rich IDE is just too good!
The 32-bit cage match
One thing has not changed since Visual Studio’s inception: it has always been a 32-bit process. This means that in the best circumstances (running as a 32-bit application on a 64-bit operating system), it can only consume close to 3 GB of memory. In practice, only about 2.5 GB of memory can be used.
Not all of that space can be used efficiently, as there’s “reserved memory” as well: memory that is pre-allocated for future use, used for communication between processes, used for mapping files from disk, chunks that do not have enough free space to be used by the .NET runtime to store its objects, …
In the remaining usable memory space, it has to host its own functionality, the Roslyn engine, and when ReSharper is installed, the ReSharper engine, too. As well as all actions and commands available throughout the IDE.
If at any point we profile Visual Studio with ReSharper enabled, we can see that a lot of time is spent doing just Garbage Collection (GC) – a good 12% of the time in this snapshot! (this was during a solution load)
(Note: as we can see in this snapshot, Visual Studio startup spends a lot of time doing Just-in-Time (JIT) compilation as well – we will touch on that later in this series.)
In 32-bit processes, GC may run more often than is optimal, for example when there is a memory shortage. In a 64-bit process, more objects may have to be sweeped but this will be done less often and at more optimal times because the ceiling is not being reached.
“If only Microsoft would compile Visual Studio as a 64-bit executable! It would ensure there’s more available memory, and everything would be fixed! Right?” Many have been asking exactly that: please make Visual Studio a 64-bit executable. The first comment in that thread mentions this will not be easy. Assemblies may become larger, and the IDE may actually become slower because there is even more memory to manage (garbage collection may take more time and resources).
How is that possible? There are many areas where Visual Studio and ReSharper consume memory, but let’s focus on one of many examples. Both Visual Studio and ReSharper keep a syntax tree in memory. All of the code in our project is parsed into a semantic model and a syntax tree. Roslyn uses immutable trees, which is great when working with syntax, but also means code under churn creates a lot of objects that may not be short-lived and will have to be garbage collected. ReSharper has a different approach with its PSI and how syntax and semantics are modeled and processed in memory.
Some syntax and semantic structures will be generated once and won’t change. So next to short-lived objects, Roslyn, as well as ReSharper end up with having a number of long-lived objects as well. And where do those go in the .NET runtime? Generation 2. And generation 2 garbage collection usually takes longer because the objects have more roots where they are referenced.
Is this bad? Not at all. Both Roslyn and ReSharper re-use already known information about our code, keeping the objects containing that information in memory. Our IDE would slow down if these had to be re-created all the time, so keeping them in memory is a good approach. It’s just that when the garbage collector runs, it may take a little longer.
Moving to a 64-bit process will only make the number of objects to scan during a garbage collection (GC) larger, possibly making GC pauses take more time.
There are many more considerations around a 64-bit Visual Studio, such as having to ensure the entire ecosystem of extensions for Visual Studio is also ported to 64-bit. Another interesting article in that regard is Visual Studio: Why is there no 64 bit version? (yet).
In short, it seems 64-bit will not happen soon, if ever. This means Microsoft as well as JetBrains and other extension authors have to resort to clever techniques to provide better performance.
In this introductory post, let’s get one more question out of the way… Many have been asking us whether JetBrains is intentionally slowing down ReSharper to promote Rider. This is definitely not the case!
Rider can not exist without ReSharper (they share their codebase), and any work we do on one product influences and benefits the other. Having both products running in a different environment (32-bit Visual Studio vs. 64-bit Rider) brings benefits, too. Any problem that exists around performance and memory in ReSharper and Rider can stay unnoticed in Rider but will most probably surface very clearly in ReSharper. Fixes will always benefit both.
ReSharper and Rider target the same audience of .NET developers, and we want to offer them a choice. Some prefer Visual Studio and ReSharper/ReSharper Ultimate, others prefer Rider. We welcome teams to work in mixed environments, where some may use vanilla Visual Studio, other use ReSharper Ultimate in Visual Studio, and another group uses Rider.
In this post, we determined that both IDE’s like Visual Studio and tools like ReSharper are complex in nature. We’ve looked at some of the histories of both products, and have seen that some of that history is not easily changed.
But that does not mean things can’t be improved! In our next blog post, we will look at a number of high-level improvements JetBrains is doing to make ReSharper better and faster. And no, the answer is not as simple as switching to Roslyn… Stay tuned for (much) more!
In the meanwhile, if you are experiencing performance issues, make sure to check the Performance guide for Visual Studio (ReSharper 2017.3+) as well as our KB article Speeding up ReSharper (and Visual Studio). If you have a specific performance issue you can reproduce, we’d appreciate if you could collect a performance snapshot and send it over.
PS: You may also enjoy the talk “.NET Performance Issues and Optimizations in Visual Studio / Roslyn / ReSharper / Rider” by Kirill Skrygan, our Rider development lead.