In previous posts of our series about ReSharper performance, we’ve looked at a number of high-level, architectural improvements JetBrains is making in terms of performance. We also discussed software tends to accumulate smaller, local performance problems over time.
In this post, we are going to highlight noticeable changes we’ve made in ReSharper 2018.1 and 2018.1.1 to improve performance for many scenarios.
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
There are some interesting gems in there. Let’s have a more in-depth look at them!
The ReSharper and Rider issue tracker
In case you didn’t know this: here at JetBrains, we have a public issue tracker for all of our products. For example, issues for ReSharper are available here, Rider issues can be seen here. Anyone can create new issues, describe how to reproduce a problem, attach screenshots, and more.
It’s also possible to create new issues from inside our products. For example, ReSharper allows collecting a performance snapshot and sending it to JetBrains. This is extremely helpful for us: using this information, we get great insights in the various environments and use cases our products are used in.
In the remainder of this post, we will look at a number of issues that were submitted by developers from all around the world. We want to thank everyone who made the effort of reporting them, and helping to make ReSharper and Rider better for everyone!
Performance issues fixed in ReSharper 2018.1
As with every release, ReSharper 2018.1 shipped with new features as well as fixes and improvements to performance issues.
RSRP-467444 – Huge delays when switching between file tabs with different effective
In the previous version, ReSharper collected indentation settings from
.clang-format, and layered-settings files for each opened code file and changed Visual Studio settings on the fly, which could sometimes be slow. As a result, switching between files caused delays.
In 2018.1, we stopped changing Visual Studio settings in accordance with
.editorconfig. Instead, we now override Enter and Tab key handlers to keep
.editorconfig file indentations. “Smart indent on Enter” and “Use ReSharper formatter settings on Tab” settings were added to ReSharper | Options | Environment | Editor | Editor Behavior.
RSRP-466697 – MvcUtil.IsPossibleViewsFolder is very slow in completion
Some time ago, we added a new scope to the File Templates feature: Razor Views Folder. Since technically there is no difference between calculating availability for File Templates and Live Templates, “Razor Views Folder” scope is calculated even when IntelliSense is called, which can take lots of time in some cases. However, there is no need for File Templates in IntelliSense popup. ReSharper now stops calculating “Razor Views Folder” scope on preparing data stage for IntelliSense popup.
RSRP-468843 – Loading 8-project solution with an Aurelia-based web site – UI freeze takes around 1 minute in total
RSRP-468809 – VS UI freeze when running lots of tests
ReSharper Unit Test runner reads assemblies with tests from disk to discover all target frameworks for every single test. It turns out that doing so involved lots of redundant I/O operations, and most tests are stored in the same assembly, there’s no need to read the file over and over again. We removed redundant I/O operations by adding an assemblies cache, and the delay went away.
RSRP-464284 – [Performance Report] Run xUnit.net unit test with data factory freezes UI on first run (this was a private ticket)
A whole tree in a unit test session tab used to be updated on adding just a few elements to the tree. We disabled full tree update when the number of modified nodes is low, and now redraw/add/remove only these nodes without touching others.
RSRP-468914 – [Performance Report] Very slow solution loading snapshot
Reading properties for C++ projects took much more time than we expected. We got in touch with our contacts at Microsoft, and they provided us guidance on how to read some of them in a quicker way to optimize access time.
RSRP-467680 – Find usages is slow
Find Usages for compiled elements (symbols from libraries) executed the same operations multiple times due to a lot of old code with a funny smell. After rewriting the code to make sure that the required data is only queried once, Find Usages became way faster.
RSRP-467639 – Slow “Derived Symbols”
We added a cache inside the “Find Inheritors” search mechanism that is at the core of Go to Derived Symbols, Find Usages and most refactorings. We are expecting all these ReSharper features to get a performance boost in code bases with huge inheritance hierarchies.
Rename refactoring – Multi-threaded Find Usages (this was a private ticket)
The Find Usages stage of the Rename refactoring now uses multithreading. Because this is the most time-consuming stage of executing a rename, expect a significant overall speed-up when using this immensely popular refactoring.
RSRP-460529 – [Performance Report] 40-second UI freeze when I in-place rename a CSS class name from an .ascx page in DNN
We moved our internal text search from a word-based index to a trigram index several releases ago. It allows us to significantly decrease the number of files for further processing since we can definitely identify which files do not contain a particular word.
Unfortunately, some of the search providers (including CSS ones) were not fully updated to get all trigram benefits and still used an old, intermediate layer between the provider and the index. We removed this intermediate layer and now CSS search providers use trigram index directly. After migration to trigram index, we can query names like “class-table-id” as a single word instead of having to divide it into separate parts.
RSRP-467468 – Set ShouldBuildPsi=False for XAML-generated files .g.cs and .i.g.cs
Before the fix, we filtered some entities from
.i.g.cs files despite already having this information from
.xaml files. Since filtering takes time, we have stopped redundantly analyzing
RSRP-468715 – [Performance] Any navigation takes about 2 seconds if Todo Explorer is opened
This is a classic example of fixing one thing and breaking something else along the way. The logic of updating the To-Do Explorer involves refreshing the explorer tree in case of changing the target framework for a file. In large solutions, updating the tree could take a long time but since changing the target framework occurs rarely in real life, we can live with that delay without anyone noticing.
After fixing another issue, we accidentally started sending “changing the target framework” event on every opening of a text control. Since any “Navigate to” action opens a text control, the To-Do Explorer tree started refreshing itself way too often.
To fix the issue, we stopped sending redundant events, and only fire them if there is an explicit target framework change.
Performance issues fixed in ReSharper 2018.1.1
The latest updates of ReSharper and Rider (version 2018.1.1) come with additional performance fixes. We did a review of all of our components that call into Visual Studio during startup, and optimized them by either postponing these calls until needed.
Unfortunately, we did introduce a regression in typing speed, a bugfix release will be made available in the very near future.
Other than that, here’s an overview of a few noteworthy changes:
RSRP-469057 – Investigate JsonSchemaCache that takes 800 MB
Caching JSON schemas in memory can be killer… Profiling a repro project, we noticed lots of JSON schema-related string duplicates – essentially wasting precious memory.
As it turns out, the JSON schema cache was not shared between projects! This has been updated, reducing memory usage for solutions that contain many projects using JSON.
RSRP-452954 – Postpone calling
VsFontsManager.UpdateFonts until editor is open
During startup, ReSharper called into Visual Studio’s
VsFontsManager.UpdateFonts() to get information about available and configured fonts. Turns out this method is not extremely fast (and who knows, being on the execution stack might even cause the yellow unresponsiveness banner to point at ReSharper)
Since ReSharper does not need this information during startup, we now lazily request this information from Visual Studio when needed. And since this will always be after Visual Studio itself initializes its editor package which provides font information, ReSharper can get font information much faster.
RSRP-457956 – VsKeyBindingsProcessor might be slow
ReSharper used to contain a component that monitored usage of key bindings, to help determine whether commands were called using a keyboard shortcut or by other means (such as a menu item or toolbar button).
When requesting key binding information from Visual Studio, ReSharper would have to wait for several VSPackages to be loaded. We have now removed this component and are using a less invasive technique to determine how a command was invoked, improving startup speed.
.cshtml files get processed during rename of private field in
This problem was introduced several releases ago when we added Tag Helpers support into Razor. As part of that support, ReSharper started looking for C# code entities in Razor files, despite the visibility modifier of a code entity. As a result, Find Usages looked in Razor files even for private members while gathering files for the Rename refactoring.
Now, we take the visibility modifier into account, and this dramatically narrows down a file’s scope for further searching.
The availability of any ReSharper refactoring depends on the cursor position. To calculate the availability for some of them, ReSharper gathers lots of information from different places. In these issues, ReSharper asks our internal database (levelDB) whether a file has language injections or not. Since levelDB is hosted on disk, this triggers lots of I/O operations which can sometimes be slow. This issue was introduced some time ago when we refactored our codebase in order to reduce memory traffic.
To fix these issues, we’ve added a cache for injected languages which decreases the number of I/O operations.
This marks the end of our series around ReSharper performance improvements that will benefit both ReSharper and Rider. We discussed a number of high-level, architectural changes that will start surfacing with the next releases of ReSharper and Rider. We also looked at specific performance fixes that were made in ReSharper 2018.1 and 2018.1.1.
We are going to make it a habit to publish blog posts about performance improvements we are making in future ReSharper release cycles. Keep an eye on our blog and Twitter!
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.