Performance reports (and other issues) from the ReSharper issue tracker

ReSharper uses caches heavily, to help us get some work done quickly if we have already done it at least once before. However, when we need to do something for the first time and the cache is empty, caching might take time.

In this issue, the Live Template cache was generated as soon as the user started typing for the first time. This led to a noticeable delay before the code completion popup appeared.

To fix this, we’ve taught ReSharper to warm up the Live Template cache in a background thread in advance – as soon as the user activates the code editor with a file. Now, the code completion popup appears immediately.

As we analyzed several snapshots, we found out that different features/algorithms contributed to processing a reference more than once, even in the same project scope. It looked as though the processing results were not shared between them, despite the fact that some data had already been cached at the lower processing levels.

We’ve added an extra high-level cache to store the entire processing results and share them, so as not to process everything once multiple times. This has led to a savings of about 400 ms on opening some particular files.

Because of a bug in reading data from cache with file templates applicable to the specific project folder, every time the cache was invalidated and recollected once again. This made it look as though there was no cache at all. We’ve fixed that, and now read/write cache operations work the way they should.

Actually, ReSharper tried to read project properties for C++ projects on solution loading, even if ReSharper C++ was not installed, so there was no need to collect this data. Starting with version 2018.2, ReSharper no longer reads properties for C++ projects. This has cut down solution loading times for solutions with C++ projects.

RSRP-470230 Web project open may be slow because of WebAssemblyReferenceFactory.ForceAddAssemblyCookie

To execute some operations in ReSharper, a task has to acquire a write lock to stop any other activities. In that particular case, reading an assembly’s parameters (mvid, etc.) acquired a write lock on a UI thread, but sometimes the reading could take a lot of time because of I/O operations. As a result, other UI activities would just sit and wait. This produced a noticeable delay on project opening.

To work around this, we’ve applied a slightly different technique. If something needs to read an assembly’s parameters, we read the assembly in a background thread, and collect the requested info. When it’s ready, we return to the UI thread, acquire a write lock, and provide this data to the requester. No noticeable delay so far.

Previously, we used a custom Concurrent Programming pattern with blocking a write lock request, which prevented other activities until the lock was released. When there was a background thread, busy with some read activity, and the IDE was trying to acquire the write lock, the read activity would be stopped and the write lock would be acquired. As a result, the IDE lost all the read activity’s results and needed to restart it after the write lock was released.

We’ve now switched to another pattern for tasks which are not urgent and can wait for some time. If a non-urgent task needs to perform some activity under a write lock but some background thread has already acquired a read lock, then the task will be postponed without interrupting the background activity.

ReSharper has lots of checks to provide you with valuable suggestions and warnings to improve your code. To run any checks, code analysis needs to acquire a read lock. However, since some checks are complicated and need lots of time to complete, this might produce some delay in executing other actions. In this particular case, typing was the victim. Typing requires acquiring a write lock but since the long checks already have a read lock, typing needs to wait for the analysis to be finished. This added a noticeable delay in appearing already typed letters.

To fix this, we’ve implemented CheckForInterrupt pattern for these long-executed inspections. Right now, all of them frequently check if any activity wants to be executed at the same time and if it is, then analysis gets interrupted.

In this case the project had lots of extension methods with a type parameter, so collecting all of them for the code completion popup was slow. We’ve reworked the part of code completion that gathers them and managed to speed up code completion appearing for this case.

Each time you type a dot after an object, ReSharper collects lots of data to present in the code completion popup. Most of the methods that gather this data use caches to store the results for further usages, so that the same work doesn’t have to be performed again. In this case, the method MemberOwner.GetMemberPresenceFlag determined whether a type of the object has, e.g., a constructor or an operator as a type member. It turned out to be a pretty simple fix: we’ve just added a new cache to store the result of first method’s execution.

Several ReSharper features, such as code completion, use the Find Inheritors search for compiled elements as a stage while gathering data to present. We analyzed the snapshots to find that this took a lot of time. After some investigation, we figured out that Equals and GetHashCode methods in our comparer were too complicated on adding a new element to HashSet and did more work than needed for this particular case. As a solution, we’ve made Equals and GetHashCode less complex, and now the Find Inheritors search delivers results faster than before.

As you may know, in the 2018.1 release Search Everywhere started showing additional matches found by Go to Text. In this issue report, when you searched for a two-symbol-long string, you would see an enormous number of Go to Text matches. This wasn’t a problem for the Search Everywhere popup, but if you called Show in Find Results for the results, gathering text search matches and presenting them as a tree took a long time.

To fix this, we’ve stopped forwarding text search matches to the Show in Find Results window in case Go to Text is not visible to a user in the Search Everywhere popup.

RSRP-458584 Remove redundant cast in file takes ages to completeRSRP-446660 Performance Problems on Fix in ScopeRSRP-463587 Remove redundant argument(s) value very slow when hundreds of files are affectedRSRP-469125 Use type keyword in solution: didn’t finish in 12 hoursRSRP-462828 Quick fix doesn’t work for a large project

There are several performance requests about the same issue: Fix in Scope (when you might select the scope and fix all occurrences of a specific inspection at once) is incredibly slow and can’t finish in acceptable time.

For the ReSharper 2017.3 release, we completed a major refactoring of our codebase under the hood. As a result, quick-fixes are now available in the code editor – even if some of the analyses is incomplete, and even if code analysis is disabled for a file. Also, we switched the engine behind the Fix in scope quick-fixes to the new one, which is faster and more powerful.

Most recently, in the 2018.2 release cycle, some quick-fixes have been moved to the new infrastructure, and we’ve reworked another area to decrease the number of running analyses while executing Fix in scope. All of these improvements have significantly sped up its execution.

RSRP-467524 Formatter is called from daemon, taking 4% of time (a private ticket)

While running some inspections on the code, ReSharper generates a lot of temp code to verify its speculations on what might be done for the current piece of code (e.g., to suggest removing some redundancies). This is to make sure it does not change the existing logic or trigger a compiler error.

Since the temp code is generated only for checks, there is no need to run the code formatter for that code. We’ve stopped invoking the formatter on temporary code and managed to slightly boost its performance.

Whew, that’s all about the specific performance fixes we’ve fixed in ReSharper 2018.2 EAP! We are going to fix a few more before releasing the 2018.2 RTM, like speeding up solution loading a bit. Expect more information about this in next blog post after the 2018.2 release and keep an eye on our blog!

@Sam, thank you for your support! We worked hard on improving performance in last releases and will continue doing it. And our apologies for the troubles ReSharper caused that keep you considering to uninstall ReSharper.

@Jeff, thank you for your support! Yes, it is a great habit to publish a performance digest for every release since we gave the ReSharper performance a high priority in the last releases. We do our best to continue putting more efforts to improve the performance.

Thanks @Alexander. I hope Jetbrains will gave ReSharper performance a high priority for every release from now on. ReSharper bring many new features on table for us which is really great. However, we have to acknowledged that not every new/existing feature is used by all developers, but I believe that performance is the most ‘common feature’ every developers is desiring.

Let’s say there are 100 developers are using R# everyday, when R# release a new feature that may be benefit for 30 of 100 developers. If this new feature cause performance issue, then this issue will impact all 100 developers, which may somehow negate the original benefit. So, please always gave R# performance a high priority, it is important for million of developers. You guys are on the right track, keeping going!