Today, we would like to tell you about our recent efforts aimed at making ReSharper faster. We’ll explore the four crucial performance improvements we’ve made in the 2018.3 release cycle:
- Refactoring the loading of assemblies
- Keyword completion
- Code completion in general
- Asynchronous value tracking
Let’s have a look!
Refactoring the loading of assemblies
Previously, ReSharper loaded all referenced assemblies for a solution regardless of whether they were direct or transitive. For example, for the RavenDB solution, the total number of assemblies loaded was about 1,100.
In the 2018.3 release cycle, we’ve refactored the loading assemblies stage so that it does not load all of the transitive closures of referenced assemblies. This means that at startup, we only load the directly referenced assemblies. If any transitive assemblies are needed later, ReSharper loads those as appropriate. Speaking about the same example, the RavenDB solution, the total number of assemblies loaded has dropped to 660.
As a result, the solution loading time is shorter and the total memory footprint is lower as well.
This change I’m going to talk about has a little visible effect, but it was really important for us to implement in order to improve your code completion experience in C# files.
A bit of history to begin with. Many years ago, in one of the first ReSharper versions we implemented the code completion suggestion of C# language keywords. A few suboptimal design decisions back then led us to an implementation involving runtime exceptions in ReSharper code.
To put it simply, to identify the list of keywords available in a particular context of user code, we triggered the parsing of the current code snippet with various keywords appended… multiple times. The parser would sometimes throw runtime exceptions (the usual way to implement parser error recovery) when it encounters an unfinished or broken piece of code. In such code snippets, this happened a lot. As a result, every single code completion invocation in C# code resulted in a small number of CLR exceptions being thrown/catched, producing some noticeable amount of CPU time spent on CLR exception handling (which is not fast at all).
In ReSharper 2018.3, we’ve reworked the C# keyword completion from scratch. Now it is done in a new way, with no additional parsing involved or runtime exceptions being thrown. We now analyze and infer the set of available keywords directly from the source code that’s currently visible in the editor. This eliminates the need to change anything in the sandbox and re-parse it to build multiple versions of syntax trees to analyze.
The latency savings are difficult to measure exactly (as CPU time for throwing runtime exceptions fluctuates a lot), but generally we’ve observed a 10-20 ms reduction in total computation time, which should hopefully lead to a more pleasant code completion experience. Expect more improvements in future updates!
Code completion in general
In ReSharper, there are a lot of providers that populate the IntelliSense popup with completion items. In some cases, the total exceeds 1 million.
Before the 2018.3 release, we sorted the whole list of items (alphabetically or by relevance, depending on how it’s set in the Options). So, a million items would take quite some time to sort before they were displayed in the code completion list.
Now, we no longer sort the list with ALL the items. Since all providers have a priority, we take only those with the highest priorities to populate the list to be sorted. Most of the time, there are fewer than 1000 items. As a result, sorting is performed much faster than before.
Asynchronous Value Tracking
Before the actual release, if you ran the Value Tracking action, it would block the UI thread and show you a modal progress dialog, while it processed the data and gathered the result. This would force you to wait until the process was completed before you could continue working on your project.
Since 2018.3, Value Tracking gathers results asynchronously, in the background. The modal dialogs are no more, and the UI thread is no longer locked. You can continue working with the project while Value Tracking collects its results.
This does not mean that the four improvements I’ve outlined above are all we’ve done to improve performance in ReSharper 2018.3. The other performance-related requests can be found on YouTrack.
Make sure to also check out the Performance guide for Visual Studio (ReSharper 2017.3+) and our KB article, Speeding up ReSharper (and Visual Studio), if you are experiencing performance issues. If you have a specific performance issue you can reproduce, we would greatly appreciate it if you could collect a performance snapshot and send it over.