Long read: Where we are with “out of process” ReSharper

A little over a year ago, we ran a series of blog posts describing performance improvements we were making to ReSharper. We’ve delivered a lot of improvements in the releases since then, but we haven’t yet delivered on the big one – running ReSharper out of process. We thought it was about time we gave you an update on where we are.

TL;DR: This isn’t an announcement post. We don’t have an ETA to share right now, although we have made significant progress on what is a massive problem. This post should give you an idea of the scale of the technical challenges we are working through in order to make ReSharper run out of process successfully.

So, grab a coffee, find a comfy chair, and we’ll explain exactly what’s going on with ReSharper out of process, why it’s taking so long, and what the future plans are.

What are we trying to achieve?

To recap, we want to push ReSharper out of process. Currently, all of ReSharper’s features – navigation, code completion, inspections, quick fixes and refactorings, unit testing, diagramming, find usages, etc. – all run inside the Visual Studio process.

This is a problem because Visual Studio is a 32-bit process, and realistically that means it can only use 2 to 2.5GB of memory. This might sound like a lot, but this memory space is also shared between your project files, the .NET runtime, ReSharper, and Visual Studio’s own features, including the WPF based UI, source control and of course any other installed plugins. And Visual Studio itself has added extra memory demands in recent versions – converting the ReSharper solution to the new SDK style .csproj files increased memory usage by 800MB!

The key point is that even if you have a monster development machine with 128 processors, SSD drives and 1TB of memory, Visual Studio will only ever use 2 to 2.5GB of memory. With larger projects, things can start to get a little cramped, garbage collection kicks in more frequently, throughput can suffer, and everyone gets frustrated – us included.

Rider is a combination of IntelliJ IDEA and ReSharper

When we built Rider, we split the architecture of the IDE, admittedly out of necessity. The front end user interface is ultimately based on the open source IntelliJ Platform, as used in IntelliJ IDEA, WebStorm, PyCharm and so on. This is a JVM application we’ve been building since 2001 and has a ton of useful features that we wanted to take advantage of. Rider’s C# features reuse ReSharper, which we’ve been building since 2004, and we wanted to keep the breadth of functionality there, too. But those two pieces are not compatible – one is a JVM application and the other is .NET.

So Rider runs the ReSharper codebase out of process, using a very lightweight inter-process communication protocol. More importantly, both the IntelliJ and ReSharper processes are 64-bit, meaning we can make much better use of the resources available to your computer.

As anyone who has used Rider will confirm, this is a very rewarding strategy for performance.

And so we want to apply this architecture to ReSharper in Visual Studio, too, and push ReSharper out of process. We’ve proved the technology works with Rider, so it should be a quick and easy job to make it work with ReSharper too, right? Sadly, things are never that straightforward.

Why is it so hard?

Did I mention that we’ve been building ReSharper since 2004? That’s a lot of functionality to migrate, and the central architecture of ReSharper is built on the assumption of running inside Visual Studio. Essentially, we’ve spent the last 15 years tightly coupling a product to Visual Studio, and we’re now having to unpick it.

On the plus side, way back in ReSharper 2.0 we introduced interfaces to abstract away from Visual Studio. These have proved invaluable for supporting multiple versions of Visual Studio simultaneously, as well as allowing us to write hundreds of thousands of tests. However, these interfaces were originally designed as a “seam” to avoid being tightly coupled to specific versions of Visual Studio’s APIs, and for the most part, they make the same assumptions as the Visual Studio APIs – the interaction is fundamentally the same.

The unreleased ReSharper 2.0 IDE
The unreleased ReSharper 2.0 IDE also made extensive use of these interfaces.

Unfortunately, the interactions required for an in-process API are not the same as what we need for out of process support. For one thing, communication needs to be asynchronous, and we all know how async can spread “virally” through a codebase. Secondly, we need the inter-process communication to be very lightweight. In fact, the protocol we’ve built for Rider, and which will be used when running ReSharper out of process, is essentially just a view model – all processing happens in the ReSharper host process, and only presentation data is sent to a thin layer running inside the IDE user interface process. This also has a big impact on the shape of the APIs we need when we move to out of process. (You can read more about Rider’s architecture in this Code magazine article.)

A great example here is a piece of work that spans previous and current release cycles on decoupling the project model. This is a phased set of refactorings that have been moving us to the right place for out of process, while still keeping everything working, and without having a risky “big bang” style rewrite.

ReSharper needs a project model – it can’t do anything without it. This is the list of projects in a solution, the list of files in a project, references, packages, compiler settings and so on. All very important context to tell ReSharper what to analyse and how to resolve external dependencies, amongst other things. It’s also a writable model, with support for adding or removing files, projects, references, settings, etc.

ReSharper’s project model is based on Visual Studio’s view of the solution, again abstracted by interfaces that provide a more friendly interface for ReSharper’s internals, and as a seam to support multiple versions of Visual Studio. But this introduces a number of problems, not all of which are related to our requirements for out of process. Significantly, creating the project model has a negative impact on solution load time, which we want to improve. This is mainly because we’re using Visual Studio COM APIs, which means we walk the entire solution structure, synchronously, on the UI thread – and the larger the project, the more expensive this becomes.

On top of that, the way we interact with these COM APIs is not a good fit for out of process support. Traditionally, we’ve been creating project model entities directly from the hierarchy APIs – instances of our IProject, IProjectFolder and IProjectFile  interfaces. These types expose and manipulate the project model, and are used throughout the codebase to work with project files and to get access to text documents and the syntactic and semantic models. Moving to out of process means that these entities, and the information they represent, must live in the out of process “backend”, and not in the thin Visual Studio plugin “frontend”.

So our first step was to introduce lightweight “descriptor” objects that can quickly map between the project model entities in the backend, and the Visual Studio APIs in the frontend. Even though we’re still in-process, we now have a conceptual boundary of APIs that use the project model descriptors, while the existing backend code uses project model entities.

When ReSharper loads a solution, it actually reads the project model from a binary, disk based cache, if available. This is significantly faster than creating it from the Visual Studio APIs, but we still need to use the APIs to walk the solution to make sure that our caches are up to date – we need to pick up any changes from your last git pull.

And that means our next refactoring step is to create these descriptors as we iterate over the COM APIs, and compare these to the cached project model we’ve already loaded. All of this still happens on the UI thread, and is also under our global “write lock” that protects our project model from threading issues. We’re not seeing any improvements yet.

From this point, things start to get a little more interesting. The third step is to use the COM APIs to walk the solution, still synchronously, but only create our lightweight descriptor objects. We then send these, asynchronously, to the (conceptual, still in-process) “backend”. Right now, this just means running on a background thread, but it also means that our impact on the UI thread is significantly reduced. On the background thread, we compare our descriptors to our cached project model, and if there are any differences, marshal back to the UI thread to take the write lock, talk to the COM APIs and update just the changes. Things are getting better.

Finally, we get to where we are in this release cycle – and we can remove the usage of the COM APIs completely. We’ve previously looked at using Roslyn’s workspaces API, but this isn’t as rich as the COM APIs, and doesn’t give us all the information we need. Instead, we’ll take a leaf out of Rider’s book, and talk to MSBuild directly.

But again, this is anything but straightforward. We can’t use the MSBuild APIs inside Visual Studio, as this causes increased memory usage. So we have to host MSBuild ourselves, as another out of process application. But now we have to be very careful to avoid synchronisation and contention issues with Visual Studio’s own project evaluation. There are also significant challenges in making sure that the project model is writable – if we use our out of process MSBuild to add a reference and update the project files, we also need to call the Visual Studio APIs to avoid Visual Studio having to reload the solution.

ReSharper options page using the new Rider style project model
ReSharper 2019.2 EAP3 will be using the new project model by default.

The good news is that the work has been completed on this piece, and we’re currently putting it through its paces with a good round of testing. We’re hoping it will be the default option in ReSharper 2019.2, but that will only happen if we’re confident of the quality. If there are issues, we’ll hold it back for ReSharper 2019.3.

I hope you can see that there’s a huge amount of complexity involved in all of this. Basically, what we’re doing with ReSharper is like changing the wheels of a car doing 130 down the motorway. We’re fundamentally changing the architecture of a product that we’ve been building for 15 years, while it’s still (very much) in active development. And we can’t even postpone other features we’re working on, such as support for new C# language features, or new versions of Visual Studio.

In fact, recent changes to the way Visual Studio and .NET 4.8 handles per-monitor DPI has required the attention of a lot of the team in an effort to quickly get all of our tool windows working with the unexpected changes to APIs. And let’s not forget the Rider team implementing more ReSharper features, as well as porting the out of process ReSharper host to run on .NET Core!

But it already works with Rider?

Rider 2017.1 release banner image

This is a very good point. Rider was released back in August 2017, and had a lengthy EAP process before that. We proved we could run ReSharper out of process a long time ago. If we’ve already got it working in Rider, why does it take so much longer to get it working with Visual Studio? And if ReSharper is based on so many assumptions about interactions with Visual Studio, how does Rider work with IntelliJ?

A simplistic response to the first point is that not all of ReSharper’s features have been integrated into Rider yet, such as our type or project dependency diagrams, or remove unused references, and we can’t move ReSharper out of process until all of these features are available to an out of process host. This is true, but it’s not the full story.

The main answer, to both of these points, is also very simple – they’re different scenarios, and have different requirements. Running ReSharper as an out of process plugin to Visual Studio is a very different proposition to running ReSharper as the out of process core to a full IDE built on top of the IntelliJ Platform.

There are fundamental differences to the way IntelliJ and Visual Studio work, and how we integrate with both. In some respects, Rider’s out of process ReSharper host is a superset of the functionality in ReSharper for Visual Studio – the Solution Explorer is a good example here. Rider needs to provide a rich hierarchy of solution, projects, folders, files, references, dependencies, icons, properties, and so on. ReSharper doesn’t need to show anything – this tool window is provided by Visual Studio. So the out of process work done for Rider can’t be reused by ReSharper for Visual Studio.

But that sounds great! If Visual Studio provides the Solution Explorer, ReSharper running out of process doesn’t need to provide anything. Not so fast! While ReSharper doesn’t own or draw the Solution Explorer, it does need to track the selected item, provide context menu actions, calculate extra properties for the selected item and so on. So we can’t just reuse this part of the existing Rider out of process infrastructure, and have to refactor existing ReSharper functionality to create something new, for a new set of requirements. The worst of all worlds.

On the flip side, unit testing support is easy. Rider doesn’t use IntelliJ’s existing unit testing UI – ReSharper’s unit testing support is handled and presented very differently to how IntelliJ does it for other languages and frameworks. So the Rider team wrote their own custom version of the unit testing tool window that does expose all of ReSharper’s unit testing features, and that means that the hard work to show unit test results from an out of process ReSharper is already done – we just need to wire that up to Visual Studio.

Looking at another example, both ReSharper and Rider show the results of the Find Usages command in a tool window. This means ReSharper already supports sending a tree of results via the out of process communication protocol. However, Rider simply needs to fulfill an existing IntelliJ API, while ReSharper needs to create the tool window from scratch. The scenarios are different.

This also raises an interesting question – where does the user interface for this custom tool window live? ReSharper will obviously still need an in-process Visual Studio plugin – should this plugin create the user interface and populate it from out of process data? Or should we have some way of “remoting” a user interface from the backend out of process host? And what about settings pages and dialogs for refactorings – who creates and owns these? Do we create one user interface in Swing for IntelliJ and another in WPF for Visual Studio?

The good news here is that there is a comprehensive chunk of Rider work that we can reuse in this case, a feature we call BeControls (the “Be” stands for “backend”). This allows us to create a view, such as a dialog or panel, from a view model created by the out of process ReSharper host, transmitted to the front end user interface process (either IntelliJ or Visual Studio) and drawn natively. Rider is already using this extensively for Rider support, and we can now write a user interface that can be shared in both Rider and ReSharper for Visual Studio. And this will also work nicely for ReSharper running out of process.

BeControls - backend controls - in action in the ReSharper and Rider codebase

But what about the problems listed above, that different scenarios require different interactions and the core interface we built to work with Visual Studio are no longer suitable for working out of process? How does Rider work if that’s such a big problem?

The answer to this one is fairly easy as well – with Rider, we own both sides of the interface contract. If there’s a problem with the front end, we can change the interfaces to work with both Visual Studio and Rider’s out of process host. And if we can’t easily change the interface, we can change the implementation or requirement in the IntelliJ side. We don’t have this flexibility with Visual Studio.

Looking at the project model, we don’t have to worry about any interaction with Visual Studio at all, so we can host MSBuild directly and take full ownership of the project model.

Or we can look at the “action” system. Each menu item, context menu item, toolbar button and keyboard shortcut is bound to an action, and ReSharper has hundreds of these. Both Visual Studio and IntelliJ have different systems for working with actions, with different requirements for registering actions, assigning shortcuts, customising presentation (icons and text) and calculating availability – some synchronous, some async, some requiring UI thread access, some done declaratively. While building Rider, we made changes to the IntelliJ Platform to make these different models more similar, so we could reuse ReSharper’s actions with only minor changes. But this required changes on both sides – ReSharper and IntelliJ.

Having full ownership of the entire IDE allows us to make any changes we need to get IntelliJ and Rider playing nicely together. But there is a cost to this strategy, too – the IntelliJ Platform is shared across 10 different IDEs inside JetBrains, not counting third party IDEs such as Android Studio. Any changes we make to IntelliJ must play nice with the IntelliJ ecosystem. Rider has been built from its own fork of the IntelliJ Platform since the very beginning, and after every release, we’ve had to backport and merge changes back upstream. In fact, Rider 2019.2 will be the first release which is built from IntelliJ’s master branch.

Where are we now?

Whew. That’s a very long detailed look at why ReSharper isn’t yet running out of process. So where does that leave us? How are things looking for the future?

Firstly: please be patient with us, but rest assured, it’s coming.

This is a massive architectural challenge, and is not something that can easily be done at speed, but it is also a big deal for us – we want it too. We’ve made a huge amount of progress, and have already got a lot of the pieces in place for running ReSharper out of process. As before, we’re not committing to an ETA of when we’ll see ReSharper running fully out of process. It’s coming, but it’s not there yet. We’ll keep you informed of progress and you’ll definitely hear about preview releases when the time comes.

But importantly, this isn’t all we’re doing to address performance in ReSharper. We understand how important it is, and continue to make improvements in every release. We’ve been targeting specific reported scenarios, as we covered in this series of posts from ReSharper 2018.2, or this post from ReSharper 2018.3.

And we’re also working on more general performance improvements for initial startup and solution load. For example, we’re hoping to ship the MSBuild based project model in ReSharper 2019.2. That should have a positive impact on solution load, similar to the now sadly deprecated Lightweight Solution Load from Visual Studio 2017.

Looking at future releases, we’re also working on changing our container construction strategy, by making it both lazy and asynchronous. Currently, we create all components at application startup and solution load, on the UI thread. This is done at low priority, frequently yielding back to the UI for responsiveness, but the impact can still be felt. We’re working to move this to a background thread, and only creating components when we need them. This is yet another massive undertaking with hidden complexities, requiring an audit of the entire codebase to see what components can be created lazily, and what must be created eagerly, as well as correctly fulfilling component dependencies on a background thread that require COM objects that can only be used on the UI thread! We’ll talk more about this in the future.

We hope you’ve enjoyed this deep dive into what’s going on with taking ReSharper out of process. We’re just as impatient as you are to get this implemented, and hope to have something to share sooner, rather than later.

In the meantime, if you do encounter performance issues with ReSharper, please use these instructions to provide us with a profiling snapshot that we can use to investigate and hopefully fix. And if you haven’t already, please try your solution with Rider – you might be pleasantly surprised!

This entry was posted in How-To's and tagged , , , , , . Bookmark the permalink.

19 Responses to Long read: Where we are with “out of process” ReSharper

  1. Jeff Chen says:

    Matt Ellis, thanks for this great post. I am really enjoy to read this.

    Also big thanks for the great effort to working on ‘R# out process’.

    • Mike-EEE says:

      I would upvote this… if this blog wasn’t stuck in 2003.

      And yes, there is a cool emoticon in the previous sentence, but it has been stripped out.

      Same for the previous sentence!

  2. tobi says:

    This is one of the toughest architectural problems that I have ever seen. Most applications are much simpler even if they have 10 times more LOC.

    The most important thing that I hope to get from future Resharper versions or from Rider is better typing and autocompletion speed. This is by far the highest priority item for me. Currently, it’s not satisfactory.

  3. Morten Mertner says:

    I’ve stopped using R# with VS (it has become unbearable) and instead use a combo of VS and Rider. Am excited about the prospect of things becoming usable again at some point.

    As for LSL, it was an extremely poor attempt to solve solution load speed. Nothing worked until you initialized the projects so all it did was postpone the delays, it did in fact nothing to optimize what was going on. It will not be missed.

  4. Stig says:

    Great that this is coming. Will getting r# out of the visual studio process mean that r# will work as language server like Omnisharp or Will it still be closely tide to visual studio? I would like to be able to use r# in VsCode and VIm and NeoVim, but this requires that r# is not tied to Visual Studio. Please elaborate om whether it Will be possible to write extensions for vscode and vim which ude r#.

    • Matt Ellis says:

      No. There are no plans for ReSharper to work with any editor other than Visual Studio or Rider. The inter process communication is not based on the language server protocol, which didn’t meet our requirements when building Rider – e.g. ReSharper has way more refactorings than the LSP defines, there’s a lot of overhead in continuous JSON serialisation, and so on. You can read more about the protocol we are using in this Code article.

      We don’t expect it to be possible to create extensions for other editors. The models we’re using to synchronise between the front end and back end are not open source, and can also be quite closely tied to the hosting application (see the discussion about the Solution Explorer in ReSharper and Rider above). Also, most editors don’t provide rich enough extensibility to expose anything more than the basic LSP features, and ReSharper has more to offer than that.

  5. Skot says:

    Fascinating article – thanks very much for taking the effort to write it, and best of luck through the rest of the project.
    I’ve got Smug Mode engaged as I’ve been on Rider exclusively for the last 12 months :)

  6. Qwerty says:

    Will there be actual new features in next major release apart from this and C#8 support? You keep adding more and more to Rider instead…

    • Matt Ellis says:

      Yes! C# 8 is a major focus for us, though, as we want to be able to provide great support for the new language features. But we also have updates to the debugger DataTips, file text search integrated into the go to file member dialog, code style + formatter, parameter name hints, dotPeek and ReSharper’s Assembly Explorer, dotMemory and a ton of updates for ReSharper C++, as well as bug fixes and targeted performance improvements.

      Of course, we are building Rider only features, for obvious reasons. But mostly, the features we add to Rider that don’t make it into ReSharper are not there because Visual Studio already has it to some degree, like the debugger, or F# support, or where it’s not feasible, like integrating Android Studio’s features for Xamarin support. Obviously we want to innovate on top of those features, and where possible, we bring them back to ReSharper, like the DataTips and the parameter hints, both of which started out as Rider only features.

      Remember that Rider is ReSharper. All of the code facing features in Rider come from ReSharper – analysis, code completion, navigation, quick fixes, refactoring and so on. Any features we add to Rider that work with your code are actually ReSharper features, and implemented in the ReSharper codebase, and so make it into ReSharper, too. And performance improvements help both products.

      You can check out the ReSharper EAP for more details. It’s also worth pointing out that the 2019.2 release cycle is shorter than usual as we’re syncing up with the release schedules of the other IDEs. Since Rider is based on the IntelliJ Platform, it makes sense to release it with the other IntelliJ IDEs. And because Rider is based on ReSharper, it makes sense to sync ReSharper releases with Rider.

      • tobi says:

        I find that Resharper is not fully supporting recent C# <= 7 features. For example, many useful tuple transformations are not implemented. Async could be supported better as well.

        Somebody needs to audit the C# 5-7 feature set and think through what is missing.

        If you are interested I can open issues about ideas that I get when coding. But really, there are low hanging fruits that you don't need customer feedback to discover them.

  7. azeek says:

    this is work Does resharper c ++ also apply?

  8. Sam says:

    “We’ve previously looked at using Roslyn’s workspaces API, but this isn’t as rich as the COM APIs, and doesn’t give us all the information we need.”

    I assume you guys have tried to work with Microsoft on this? It really sucks that you need to do such a complex workaround because VS’ APIs are lacking.

    • Daniel says:

      I was wondering the same while reading this article. Roslyn and MSBuild are open source. I wonder what is easier: Contributing to those projects the necessary extensions, or developing a custom solution with another out-of-process MSBuild host and complex sync systems. Rolyn also somehow needs to be integrated with the VS project system, it would be surprising if there would not be a dedicated area in Roslyn/MSBuild where JB can add the missing pieces.

      The COM APIs of Visual Studio are really a nightmare, especially if you want to support multiple VS versions. Personally I would not mind if the out-of-process R# would only be compatible with VS2019+. Old VS can keep using in-process (for now?). Of course this only works if VS2019 offers better APIs that suit the new architecture. I know that Microsoft is also heavily pushing forward task-async APIs within the VS codebase.

      • Matt Ellis says:

        Yes, we spoke with Microsoft about this, but our requirements didn’t fit with the direction they wanted to take the API.

  9. Dmitri says:

    Thanks for an epic post. I hope the back-end separation is only a first step for what’s obviously still to come – moving the back-end accross network boundaries and making it shared between clients, i.e., if several developers are using same large library, there’s no need to re-analyze it more than once.

  10. Mark says:

    This blog post is a very welcome update. Like many, I’ve stopped using Resharper due to unacceptable performance, but will gladly jump back on board if an out-of-process version becomes available.

Leave a Reply

Your email address will not be published. Required fields are marked *