Messen der Auswirkungen der Erweiterung beim Start
Konzentrieren Sie sich auf die Erweiterungsleistung in Visual Studio 2017
Basierend auf Kundenfeedback wurde eine der Fokusbereiche für die Visual Studio 2017-Version als Start- und Lösungsladeleistung verwendet. Als Visual Studio-Plattformteam haben wir daran gearbeitet, die Leistung beim Laden von Start und Lösung zu verbessern. Im Allgemeinen schlagen unsere Messungen vor, dass installierte Erweiterungen auch erhebliche Auswirkungen auf diese Szenarien haben können.
Um Benutzern zu helfen, diese Auswirkungen zu verstehen, haben wir in Visual Studio ein neues Feature hinzugefügt, um Benutzer über langsame Erweiterungen zu informieren. Manchmal erkennt Visual Studio eine neue Erweiterung, die das Laden der Lösung oder den Start verlangsamt. Wenn eine Verlangsamung erkannt wird, wird Benutzern in der IDE eine Benachrichtigung angezeigt, die sie auf das neue Dialogfeld "Visual Studio Performance verwalten" zeigt. Auf dieses Dialogfeld kann auch immer über das Hilfemenü zugegriffen werden, um zuvor erkannte Erweiterungen zu durchsuchen.
Dieses Dokument soll Erweiterungsentwicklern helfen, indem sie beschreiben, wie die Auswirkungen der Erweiterung berechnet werden. In diesem Dokument wird auch beschrieben, wie die Auswirkungen der Erweiterung lokal analysiert werden können. Die lokale Analyse der Auswirkungen auf die Erweiterung bestimmt, ob eine Erweiterung möglicherweise als Leistungsbeeinträchtigung der Erweiterung angezeigt wird.
Hinweis
Dieses Dokument konzentriert sich auf die Auswirkungen von Erweiterungen auf das Laden von Start und Lösung. Erweiterungen wirken sich auch auf die Leistung von Visual Studio aus, wenn sie dazu führen, dass die Benutzeroberfläche nicht mehr reagiert. Weitere Informationen zu diesem Thema finden Sie unter How to: Diagnose von UI-Verzögerungen durch Erweiterungen.
Auswirkungen auf den Start von Erweiterungen
Eine der am häufigsten verwendeten Methoden für Erweiterungen, um die Startleistung zu beeinträchtigen, besteht darin, das automatische Laden in einem der bekannten Start-UI-Kontexte wie NoSolutionExists oder ShellInitialized auszuwählen. Diese UI-Kontexte werden beim Start aktiviert. Alle Pakete, die das Attribut in ihre ProvideAutoLoad
Definition mit diesen Kontexten einschließen, werden zu diesem Zeitpunkt geladen und initialisiert.
Wenn wir die Auswirkungen einer Erweiterung messen, konzentrieren wir uns in erster Linie auf die zeitverwendeten Erweiterungen, die sich für das automatische Laden in den oben genannten Kontexten entscheiden. Die gemessenen Zeiten umfassen, sind jedoch nicht auf Folgendes beschränkt:
- Laden von Erweiterungsassemblys für synchrone Pakete
- Zeitaufwand für den Paketklassenkonstruktor für synchrone Pakete
- Zeitaufwand für die Methode "Package Initialize" (oder "SetSite") für synchrone Pakete
- Bei asynchronen Paketen werden die oben genannten Vorgänge im Hintergrundthread ausgeführt. Daher werden die Vorgänge von der Überwachung ausgeschlossen.
- Zeitaufwand für asynchrone Arbeit, die während der Paketinitialisierung für die Ausführung auf Standard Thread geplant ist
- In Ereignishandlern aufgewendete Zeit, insbesondere shell initialisierte Kontextaktivierung oder Änderung des Shell-Zombie-Zustands
- Ab Visual Studio 2017 Update 3 starten wir auch die Überwachungszeit für Leerlaufaufrufe, bevor die Shell initialisiert wird. Lange Vorgänge in Leerlaufhandlern verursachen auch eine nicht reagierende IDE und tragen zur wahrgenommenen Startzeit durch den Benutzer bei.
Wir haben viele Features hinzugefügt, die ab Visual Studio 2015 beginnen. Diese Features helfen beim Entfernen der Notwendigkeit, dass Pakete automatisch geladen werden müssen. Die Features verschieben auch die Notwendigkeit, dass Pakete in spezifischere Fälle geladen werden müssen. Diese Fälle umfassen Beispiele, in denen Benutzer die Erweiterung verwenden oder eine Erweiterungswirkung beim automatischen Laden verringern würden.
Weitere Details zu diesen Features finden Sie in den folgenden Dokumenten:
Regelbasierte UI-Kontexte: Ein umfassenderes regelbasiertes Modul, das um UI-Kontexte aufgebaut ist, ermöglicht es Ihnen, benutzerdefinierte Kontexte basierend auf Projekttypen, Aromen und Attributen zu erstellen. Benutzerdefinierte Kontexte können verwendet werden, um ein Paket in spezifischeren Szenarien zu laden. Diese spezifischen Szenarien umfassen das Vorhandensein eines Projekts mit einer bestimmten Funktion anstelle des Starts. Benutzerdefinierte Kontexte ermöglichen auch die Bindung der Befehlssichtbarkeit an einen benutzerdefinierten Kontext basierend auf Projektkomponenten oder anderen verfügbaren Ausdrücken. Dieses Feature beseitigt die Notwendigkeit, ein Paket zu laden, um einen Befehlsstatusabfragehandler zu registrieren.
Asynchrone Paketunterstützung: Die neue AsyncPackage-Basisklasse in Visual Studio 2015 ermöglicht es Visual Studio-Paketen, asynchron im Hintergrund zu laden, wenn die Paketlast von einem Attribut für das automatische Laden oder einer asynchronen Dienstabfrage angefordert wurde. Mit diesem Hintergrundladevorgang kann die IDE reaktionsfähig bleiben. Die IDE ist reaktionsfähig, auch wenn die Erweiterung im Hintergrund initialisiert wird und kritische Szenarien wie Start- und Lösungsladevorgang nicht beeinträchtigt werden.
Asynchrone Dienste: Mit asynchroner Paketunterstützung haben wir auch Unterstützung für Abfragen von Diensten asynchron hinzugefügt und können asynchrone Dienste registrieren. Wichtiger ist, dass wir daran arbeiten, kerne Visual Studio-Dienste zu konvertieren, um asynchrone Abfragen zu unterstützen, sodass der Großteil der Arbeit in einer asynchronen Abfrage in Hintergrundthreads auftritt. SComponentModel (Visual Studio MEF-Host) ist einer der wichtigsten Dienste, die jetzt asynchrone Abfragen unterstützen, damit Erweiterungen das asynchrone Laden vollständig unterstützen können.
Verringern der Auswirkungen von automatisch geladenen Erweiterungen
Wenn ein Paket beim Start immer noch automatisch geladen werden muss, ist es wichtig, die Während der Paketinitialisierung erledigte Arbeit zu minimieren. Die Minimierung der Paketinitialisierung reduziert die Wahrscheinlichkeit, dass sich die Erweiterung auf den Start auswirkt.
Einige Beispiele, die dazu führen können, dass die Paketinitialisierung teuer ist:
Verwenden des synchronen Paketladevorgangs anstelle des asynchronen Paketladevorgangs
Da synchrone Pakete standardmäßig im Standard Thread geladen werden, empfehlen wir Erweiterungsbesitzern, die automatisch geladene Pakete enthalten, stattdessen die asynchrone Paketbasisklasse als Erwähnung früher zu verwenden. Das Ändern eines automatisch geladenen Pakets zur Unterstützung des asynchronen Ladens erleichtert auch das Beheben der anderen unten aufgeführten Probleme.
Synchrone Datei-/Netzwerk-E/A-Anforderungen
Im Idealfall sollte jede synchrone Datei- oder Netzwerk-E/A-Anforderung im Standard Thread vermieden werden. Ihre Auswirkungen hängen vom Zustand des Computers ab und können in einigen Fällen für längere Zeit blockiert werden.
Die Verwendung asynchroner Paketladevorgänge und asynchroner IO-APIs sollte sicherstellen, dass die Paketinitialisierung den Standard Thread in solchen Fällen nicht blockiert. Benutzer können auch weiterhin mit Visual Studio interagieren, während E/A-Anforderungen im Hintergrund auftreten.
Frühe Initialisierung von Diensten, Komponenten
Eines der allgemeinen Muster bei der Paketinitialisierung besteht darin, Dienste zu initialisieren, die von diesem Paket im Paket oder initialize
der Methode constructor
verwendet oder bereitgestellt werden. Dies stellt zwar sicher, dass Dienste verwendet werden können, es kann aber auch unnötige Kosten zum Packen des Ladens hinzufügen, wenn diese Dienste nicht sofort verwendet werden. Stattdessen sollten solche Dienste bei Bedarf initialisiert werden, um die Arbeit bei der Paketinitialisierung zu minimieren.
Für globale Dienste, die von einem Paket bereitgestellt werden, können Sie Methoden verwenden, die eine Funktion verwenden AddService
, um den Dienst nur dann zu initialisieren, wenn er von einer Komponente angefordert wird. Für Dienste, die innerhalb des Pakets verwendet werden, können Sie Lazy<T> oder AsyncLazy<T> verwenden, um sicherzustellen, dass Dienste bei der ersten Verwendung initialisiert/abgefragt werden.
Messen der Auswirkungen automatisch geladener Erweiterungen mithilfe des Aktivitätsprotokolls
Ab Visual Studio 2017 Update 3 enthält das Visual Studio-Aktivitätsprotokoll jetzt Einträge für die Leistungseinbußen von Paketen während des Start- und Lösungsladevorgangs. Um diese Messungen anzuzeigen, müssen Sie Visual Studio mit der Option "/log" öffnen und die Datei "ActivityLog.xml" öffnen.
Im Aktivitätsprotokoll befinden sich die Einträge unter der Quelle "Visual Studio Performance verwalten", und sieht wie im folgenden Beispiel aus:
Component: 3cd7f5bf-6662-4ff0-ade8-97b5ff12f39c, Inclusive Cost: 2008.9381, Exclusive Cost: 2008.9381, Top Level Inclusive Cost: 2008.9381
Dieses Beispiel zeigt, dass ein Paket mit GUID "3cd7f5bf-6662-4ff0-ade8-97b5ff12f39c" 2008 ms beim Start von Visual Studio aufgewendet hat. Beachten Sie, dass Visual Studio die Kosten auf oberster Ebene als primäre Zahl betrachtet, wenn die Auswirkungen eines Pakets berechnet werden, da die Benutzer beim Deaktivieren der Erweiterung für dieses Paket sehen würden.
Messen der Auswirkungen automatisch geladener Erweiterungen mithilfe von PerfView
Während die Codeanalyse dazu beitragen kann, Codepfade zu identifizieren, die die Paketinitialisierung verlangsamen können, können Sie die Ablaufverfolgung auch mithilfe von Anwendungen wie PerfView nutzen, um die Auswirkungen einer Paketlast im Visual Studio-Start zu verstehen.
PerfView ist ein systemweites Ablaufverfolgungstool. Dieses Tool hilft Ihnen, heiße Pfade in einer Anwendung zu verstehen, entweder aufgrund der CPU-Auslastung oder beim Blockieren von Systemaufrufen. Nachfolgend finden Sie ein schnelles Beispiel für die Analyse einer Beispielerweiterung mithilfe von PerfView.
Beispielcode:
Dieses Beispiel basiert auf dem folgenden Beispielcode, der so konzipiert ist, dass einige häufige Verzögerungsursachen auftreten:This example is based on the sample code below, which is designed to show case some common delay causes:
protected override void Initialize()
{
// Initialize a class from another assembly as an example
MakeVsSlowServiceImpl service = new MakeVsSlowServiceImpl();
// Costly work in main thread involving file IO
string systemPath = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
foreach (string file in Directory.GetFiles(systemPath))
{
DateTime creationDate = File.GetCreationTime(file);
}
// Costly work after shell is initialized. This callback executes on main thread
KnownUIContexts.ShellInitializedContext.WhenActivated(() =>
{
DoMoreWork();
});
// Start async work on background thread
DoAsyncWork().Forget();
}
private async Task DoAsyncWork()
{
// Switch to background thread to do expensive work
await TaskScheduler.Default;
System.Threading.Thread.Sleep(500);
}
private void DoMoreWork()
{
// Costly work
System.Threading.Thread.Sleep(500);
// Blocking call to an asynchronous work.
ThreadHelper.JoinableTaskFactory.Run(async () => { await DoAsyncWork(); });
}
Aufzeichnen einer Ablaufverfolgung mit PerfView:
Nachdem Sie Ihre Visual Studio-Umgebung mit der installierten Erweiterung eingerichtet haben, können Sie eine Ablaufverfolgung des Startvorgangs aufzeichnen, indem Sie PerfView öffnen und das Dialogfeld "Sammeln" über das Menü "Sammeln" öffnen.
Die Standardoptionen stellen Aufrufstapel für den CPU-Verbrauch bereit, da wir jedoch auch die Zeit blockieren möchten, sollten Sie auch Threadzeitstapel aktivieren. Sobald die Einstellungen fertig sind, können Sie auf "Startauflistung " klicken und dann Visual Studio öffnen, nachdem die Aufzeichnung gestartet wurde.
Bevor Sie die Sammlung beenden, möchten Sie sicherstellen, dass Visual Studio vollständig initialisiert ist, das fenster Standard vollständig sichtbar ist und wenn die Erweiterung ui-Teile enthält, die automatisch angezeigt werden, sind sie ebenfalls sichtbar. Wenn Visual Studio vollständig geladen und Die Erweiterung initialisiert wird, können Sie die Aufzeichnung beenden, um die Ablaufverfolgung zu analysieren.
Analysieren einer Ablaufverfolgung mit PerfView:
Sobald die Aufzeichnung abgeschlossen ist, öffnet PerfView automatisch die Ablaufverfolgungs- und Erweiterungsoptionen.
Im Rahmen dieses Beispiels sind wir Standard interessiert an der Ansicht "Threadzeitstapel", die Sie unter "Erweiterte Gruppe" finden können. In dieser Ansicht wird die Gesamtzeit für einen Thread durch eine Methode angezeigt, einschließlich CPU-Zeit und blockierter Zeit, z. B. Datenträger-E/A oder Warten auf Handles.
Beim Öffnen der Threadzeitstapel-Ansicht sollten Sie den Devenv-Prozess auswählen, um die Analyse zu starten.
PerfView enthält detaillierte Anleitungen zum Lesen von Threadzeitstapeln unter einem eigenen Hilfemenü zur detaillierteren Analyse. Für dieses Beispiel möchten wir diese Ansicht weiter filtern, indem wir nur Stapel mit dem Namen des Paketmoduls und dem Startthread einschließen.
- Legen Sie GroupPats auf leeren Text fest, um standardmäßig hinzugefügte Gruppierungen zu entfernen.
- Legen Sie IncPats fest, um zusätzlich zum vorhandenen Prozessfilter Einen Teil des Assemblynamens und Startthreads einzuschließen . In diesem Fall sollte es devenv sein ; Startthread; MakeVsSlowExtension.
Jetzt zeigt die Ansicht nur die Kosten an, die den Assemblys im Zusammenhang mit der Erweiterung zugeordnet sind. In dieser Ansicht bezieht sich jedes Mal, das unter der Spalte Inc (Inklusive Kosten) des Startthreads aufgeführt ist, auf unsere gefilterte Erweiterung und wirkt sich auf den Start aus.
Für das beispiel oben sind einige interessante Aufrufstapel:
E/A unter Verwendung
System.IO
der Klasse: Obwohl die Kosten für diese Frames möglicherweise nicht zu teuer in der Ablaufverfolgung sind, stellen sie eine mögliche Ursache für ein Problem dar, da die Datei-E/A-Geschwindigkeit von Computer zu Computer variieren wird.Blockieren von Aufrufen, die auf andere asynchrone Arbeit warten: In diesem Fall würde inklusive Zeit den Zeitpunkt darstellen, zu dem der Standard Thread beim Abschluss asynchroner Arbeit blockiert wird.
Eine der anderen Ansichten in der Ablaufverfolgung, die hilfreich sein wird, um auswirkungen zu bestimmen, ist die Bildladestapel. Sie können dieselben Filter wie auf die Ansicht "Threadzeitstapel" anwenden und alle Assemblys ermitteln, die aufgrund des Codes, der von Ihrem automatisch geladenen Paket ausgeführt wird, geladen wurden.
Es ist wichtig, die Anzahl der geladenen Assemblys innerhalb einer Paketinitialisierungsroutine zu minimieren, da jede zusätzliche Assembly zusätzliche Datenträger-E/A umfasst, wodurch der Start auf langsameren Computern erheblich verlangsamt werden kann.
Zusammenfassung
Der Start von Visual Studio war einer der Bereiche, zu denen wir ständig Feedback erhalten. Unser Ziel, wie bereits erwähnt, besteht darin, dass alle Benutzer unabhängig von Komponenten und Erweiterungen, die sie installiert haben, eine konsistente Starterfahrung haben. Wir möchten mit Erweiterungsbesitzern zusammenarbeiten, um uns dabei zu helfen, dieses Ziel zu erreichen. Die oben genannten Anleitungen sollten hilfreich sein, um auswirkungen auf den Start einer Erweiterung zu verstehen und entweder zu vermeiden, dass das automatische Laden oder das asynchrone Laden erforderlich ist, um die Auswirkungen auf die Benutzerproduktivität zu minimieren.