1/ @DesignPuddle asked why Solution Explorer search is so much slower than Goto All when searching for files. This is seemingly nonsensical because Goto All is presumably searching for everything while solution explorer search is searching less, right? Well… it is nonsensical,
2/ the search should be much faster, and the reason it’s slow has a lot to do with how it was initially designed versus what it’s now being used for. So, lets dig in a bit.
Both Goto All and Search in Solution Explorer are extensible via providers. However, the providers are
3/ different, and that’s critical to understand the difference. Search in Solution Explorer was added in VS 2012 as I recall. It works by kicking off an asynchronous task that invokes each provider. It currently does this sequentially instead of in parallel. There are really two
4/ main default providers, GraphSearchProvider and HierarchySearchProvider. GraphSearchProvider is used to enumerate symbols within files, while HierarchySearchProvider is used to search through ‘top level’ nodes in Solution Explorer that you’d traditionally associate with files.
5/ Neither of these is a file search provider. Since you were asking specifically about files, we should talk about HierarchySearchProvider. We often think about solution explorer as a convenient view of files within projects; however, that doesn’t really capture what it does
6/ exactly. The Solution Explorer is a view over top of the projects within the solution and is virtual; meaning that there no requirement that it reflects files on disk, or really, any persistent asset at all. That might sound like splitting hairs but it’s central to the
7/ question. In many cases project systems will show nodes that are ‘virtual’ and do not represent items on disk. This is completely extensible. So, the HierarchySearchProvider was implemented to enumerate the only thing that represented those virtual items – the visual tree.
8/ That tree is represented by IVsHierarchyItems, hence the HierarchySearchProvider name. IVsHierarchy is implemented as an STA COM object, so to retrieve the hierarchy items, the provider must RPC to the UI thread (since it’s invoked asynchronously). This is mitigated in many
9/ ways in VS, with caches that allow the data model to be accessed in a free-threaded manner; however, those caches are not always up to date – particularly on solution open when you may reasonably want to start with a search. So, one reason that search in Solution Explorer is
10/ slower than in Goto All is because enumerating ‘files’ actually enumerates all hierarchy/ui items as they may be virtual but still relevant (for example, you can search for solution folder names which are items in the solution file and don’t exist on disk at all). In the Goto
11/ All case there is a specific FileNameItemProvider, and while it still relies on IVsHierarchy, it’s optimized to only search for file names and is more efficient in many cases.
I mentioned that there were two main providers for Solution Explorer Search. The GraphSearchProvide
12/ enumerates symbols within files (the nodes in Solution Explorer that you see when you expand within a file). Again, this provider is different than the symbol provider that is used for Goto All, similarly because the belief is that folks expect the search in solution explorer
13/ to return nodes that would be rendered in Solution Explorer, which is probably reasonable. However, those nodes are provided via a separate feature/service than the symbols in Goto All, which are provided directly by Roslyn. The Graph similarly uses a hierarchy walk to
14/ enumerate the results, though it walks them in a different way – still UI bound when retrieving the items. As I mentioned the Solution Explorer search invokes the providers serially, and unfortunately, it currently invokes the GraphSearchProvider first – so that one must
15/ complete before it starts returning file items (note: oddly, this doesn’t have as much of an impact as you might expect but does contribute).
There are two other interesting differences between the results provided by Solution Explorer search and Goto All that are somewhat
16/ historical. The GraphSearchProvider returns multiple items that look identical when you’re targeting multiple target frameworks. This isn’t a super common case, but it can be confusing when it occurs. This also slows down search as it essentially multiplies the results. The
17/ more egregious case is when partial types are heavily used. This is very evident if you happen to be using a solution like Roslyn. In this case every part of the partial type shows all symbols for the entire type. This is like what you’d see in the navbar for a partial type;
18/ however, in this case, the items are duplicated across all file nodes. This may be reasonable and useful if you’re expanding nodes when navigating via the mouse in Solution Explorer; however, in the search case, it means that you’re searching through many duplicate nodes. As
19/ an example of how ridiculous that can get, searching for “document” in the Roslyn solution returns 380 results in Goto All, and almost 11k for Solution Explorer search.
For Solution Explorer search, the results are themselves hierarchy items. Therefore, the providers must
20/ construct trees that will then be displayed as they are returned. This increases the results returned (in terms of nodes to be rendered) and makes incremental updates a little bit more wonky across providers as aggregation may need to be done within an existing hierarchy item
21/ The items that I describe above are things that we need to fix. However, depending on your scenario, there are a couple of things you can do to dramatically improve the performance of solution explorer search. If you click the drop down on the right of the solution explore
22/ search, you’ll see two search options that are checked by default: “Search within file contents” and “Search within external items.”
Search within file contents maps to the GraphSearchProvider described above and controls whether symbols are retrieved. If you are only
23/ interested in filenames, this can dramatically improve the performance. Search within external items is also checked by default and can be a big perf win for C++ projects if you aren’t interested in searching the external items list. One thing you cannot completely control is
24/ that solution explorer search will return top-level referenced assembly names which may not be your intent.
I’m not trying to defend Solution Explorer search performance, simply explaining why it’s currently different. For small projects it’s unlikely to make a noticeable
25/ difference; however, for larger projects, using Ctrl+T is going to be a better approach for the moment. It’s an area that we’re investigating to find the biggest wins that we can make while maintaining expectations for the kinds of results people expect.
I’m hopeful that we’ll be able to improve it significantly in the future. In that vein, a few of these are either already fixed in Dev17 previews or will be soon, so definitely give these a try when you get the chance 😊
• • •
Missing some Tweet in this thread? You can try to
force a refresh
1/ It's been very busy at MS ever since the new year started, and you've probably seen a bit of what we're working on with Visual Studio 2022 (devblogs.microsoft.com/visualstudio/v…).
2/ I'm going to go further back in history though, and talk about a defining moment for Visual Studio .NET (2002) and really all of Microsoft. In early 2002, Bill Gates sent a memo to the entire company that kicked off the Trustworthy Computing initiative.
3/ There had recently been a raft of serious computer viruses, coupled with the September 11th terrorist attacks; it was a turning point for the country, industry, and company.
One of the stories that my first manager used to tell, that I always got a kick out of was the following. Context: Think Week was a week that Bill Gates used to take every year to learn about a huge variety of topics, and folks at MS would submit papers/bits. It was a big deal.
"I joined Microsoft on 3/19/99 and found out I was working on a new language. The first week was a blur just getting up to speed and I don't think I even installed the complier (not that it did much then - I think you could define an interface but not use it yet).
In my second week (3/27/99) I get an email from Drew saying that Bill [Gates] wants the C# Language spec and the latest build of the compiler by 4.30 (Note that it was 4.30 not 4/30). I'm still getting used to US dates so I see 4.30 and think it's 4:30pm that day.
Incremental rebuild was a feature of the C# compiler which was meant to increase the throughput after an initial build. It worked on the principle that changes in between builds are localized, and that the information gathered by the compiler from previous builds wouldn't be
entirely invalidated; specifically, some of the information and, indeed, the assembly itself could be updated in an incremental fashion resulting in faster builds.
Both the VS 2002 and VS 2003 compilers exposed this option through the /incr switch on the command line, and the ‘Incremental Rebuild’ option in the Advanced tab of Project Properties. In 2002 incremental rebuild was enabled by default for all project types.
In 2001 I had only recently joined Microsoft full time, so I was really just getting my feet underneath me in the org. There were many internal teams using C#, so one of the things I owned was an internal DL called CSharp User Community which had thousands of folks on it.
The point of the DL was for C# users to ask questions of each other to get help as they needed it, but I did participate a lot as often folks would ask for definitive answers. The downside to this is that I often received a large number of mails throughout the day directly.
If I'd had some more experience, I would have added the user community back to the threads much more often than I did. Regardless, this ownership led to some funny and uncomfortable situations.
I was trying to remember any interesting event associated with a new year and the best I could come up with this morning is many years after what I've been tweeting about, in 2010. In 2010 we were working on Dev11 (VS 2012) and iterating closely with Windows on Windows 8.
I was leading a team to create a tooling experience for JavaScript Windows Store apps. Windows 8 was the introduction of the Windows Store and the new WinRT APIs, ABI format, etc. that allowed languages like JS, C#, VB .NET, C++, etc. to directly call the Windows API.
It was still early in Dev11 development, and my team was writing a new JavaScript language service (as well as a new project system). There was already an existing JS language service.
1/ In early 2004 we were heads down executing on Edit and Continue across a large contingent of teams. There had been several iterations of scoping, redesigns, and customer feedback.
2/ We had a weekly meeting every Thursday morning when representatives from each of the teams would get together and review progress. It was fairly heavy weight, but there were so many teams involved that it was necessary to have a regular sync.
3/ Regardless, E&C was coalescing, but teams were stretched thin working diligently to enable scenarios, improve performance, fix bugs, etc. It had been a month or so since we decided to add support for C# to the matrix as well, so folks were a bit stressed.