Misurazione dell'impatto dell'estensione nell'avvio
Concentrarsi sulle prestazioni delle estensioni in Visual Studio 2017
In base al feedback dei clienti, una delle aree di interesse per la versione di Visual Studio 2017 è stata avviata e le prestazioni di caricamento delle soluzioni. Il team della piattaforma di Visual Studio sta lavorando per migliorare le prestazioni di caricamento delle soluzioni e di avvio. In generale, le misurazioni suggeriscono che le estensioni installate possono avere un notevole impatto su tali scenari.
Per aiutare gli utenti a comprendere questo impatto, è stata aggiunta una nuova funzionalità in Visual Studio per notificare agli utenti le estensioni lente. In alcuni casi, Visual Studio rileva una nuova estensione che rallenta il caricamento o l'avvio della soluzione. Quando viene rilevato un rallentamento, gli utenti visualizzeranno una notifica nell'IDE che li indirizza alla nuova finestra di dialogo "Gestisci prestazioni di Visual Studio". È anche possibile accedere a questa finestra di dialogo dal menu ? per esplorare le estensioni rilevate in precedenza.
Questo documento mira a aiutare gli sviluppatori di estensioni descrivendo il modo in cui viene calcolato l'impatto dell'estensione. Questo documento descrive anche come analizzare l'impatto dell'estensione in locale. L'analisi locale dell'impatto dell'estensione determinerà se un'estensione può essere visualizzata come estensione che influisce sulle prestazioni.
Nota
Questo documento è incentrato sull'impatto delle estensioni sul carico di avvio e soluzione. Le estensioni influiscono anche sulle prestazioni di Visual Studio quando causano la mancata risposta dell'interfaccia utente. Per altre informazioni su questo argomento, vedere Procedura: Diagnosticare i ritardi dell'interfaccia utente causati dalle estensioni.
Impatto dell'avvio delle estensioni
Uno dei modi più comuni per le estensioni per influire sulle prestazioni di avvio consiste nel scegliere di caricare automaticamente in uno dei contesti noti dell'interfaccia utente di avvio, ad esempio NoSolutionExists o ShellInitialized. Questi contesti dell'interfaccia utente vengono attivati durante l'avvio. Tutti i pacchetti che includono l'attributo ProvideAutoLoad
nella definizione con tali contesti verranno caricati e inizializzati in quel momento.
Quando si misura l'impatto di un'estensione, ci concentriamo principalmente sul tempo dedicato alle estensioni che scelgono di caricare automaticamente nei contesti precedenti. I tempi misurati includono, ma non sono limitati a:
- Caricamento di assembly di estensione per pacchetti sincroni
- Tempo impiegato nel costruttore della classe del pacchetto per i pacchetti sincroni
- Tempo impiegato nel metodo Initialize (o SetSite) del pacchetto per i pacchetti sincroni
- Per i pacchetti asincroni, le operazioni precedenti vengono eseguite sul thread in background. Di conseguenza, le operazioni vengono escluse dal monitoraggio.
- Tempo trascorso in qualsiasi lavoro asincrono pianificato durante l'inizializzazione del pacchetto da eseguire nel thread principale
- Tempo impiegato nei gestori eventi, in particolare l'attivazione del contesto inizializzato della shell o la modifica dello stato zombie della shell
- A partire da Visual Studio 2017 Update 3, verrà avviato anche il monitoraggio del tempo dedicato alle chiamate inattive prima dell'inizializzazione della shell. Le operazioni lunghe nei gestori inattivi causano anche l'annullamento della risposta dell'IDE e contribuiscono al tempo di avvio percepito dall'utente.
Sono state aggiunte molte funzionalità a partire da Visual Studio 2015. Queste funzionalità consentono di rimuovere la necessità di caricare automaticamente i pacchetti. Le funzionalità rimandano anche la necessità di caricare i pacchetti in casi più specifici. Questi casi includono esempi in cui gli utenti sarebbero più sicuri di usare l'estensione o ridurre un impatto sull'estensione durante il caricamento automatico.
Per altre informazioni su queste funzionalità, vedere i documenti seguenti:
Contesti dell'interfaccia utente basati su regole: un motore basato su regole più completo basato su contesti dell'interfaccia utente consente di creare contesti personalizzati in base a tipi di progetto, sapori e attributi. I contesti personalizzati possono essere usati per caricare un pacchetto in scenari più specifici. Questi scenari specifici includono la presenza di un progetto con una funzionalità specifica anziché l'avvio. I contesti personalizzati consentono anche di collegare la visibilità dei comandi a un contesto personalizzato in base ai componenti del progetto o ad altri termini disponibili. Questa funzionalità elimina la necessità di caricare un pacchetto per registrare un gestore di query sullo stato del comando.
Supporto del pacchetto asincrono: la nuova classe di base AsyncPackage in Visual Studio 2015 consente il caricamento asincrono dei pacchetti in background se il caricamento del pacchetto è stato richiesto da un attributo di caricamento automatico o da una query asincrona del servizio. Questo caricamento in background consente all'IDE di rimanere reattivo. L'IDE è reattivo anche quando l'estensione viene inizializzata in background e scenari critici come l'avvio e il caricamento della soluzione non saranno interessati.
Servizi asincroni: con il supporto asincrono dei pacchetti, è stato aggiunto anche il supporto per l'esecuzione di query sui servizi in modo asincrono e la possibilità di registrare i servizi asincroni. In particolare, si sta lavorando alla conversione dei servizi principali di Visual Studio per supportare query asincrone in modo che la maggior parte del lavoro in una query asincrona si verifichi nei thread in background. SComponentModel (host MEF di Visual Studio) è uno dei servizi principali che ora supporta la query asincrona per consentire alle estensioni di supportare completamente il caricamento asincrono.
Riduzione dell'impatto delle estensioni caricate automaticamente
Se un pacchetto deve ancora essere caricato automaticamente all'avvio, è importante ridurre al minimo il lavoro svolto durante l'inizializzazione del pacchetto. Ridurre al minimo il lavoro di inizializzazione del pacchetto riduce le probabilità che l'estensione influisca sull'avvio.
Alcuni esempi che potrebbero causare un'inizializzazione del pacchetto dispendiosa sono:
Uso del caricamento sincrono del pacchetto anziché del caricamento asincrono dei pacchetti
Poiché i pacchetti sincroni vengono caricati nel thread principale per impostazione predefinita, incoraggiamo i proprietari delle estensioni che hanno pacchetti caricati automaticamente per usare la classe base del pacchetto asincrona, come indicato in precedenza. La modifica di un pacchetto caricato automaticamente per supportare il caricamento asincrono renderà anche più semplice risolvere gli altri problemi seguenti.
Richieste di I/O di rete/file sincrone
Idealmente, qualsiasi richiesta di I/O di rete o file sincrono deve essere evitata nel thread principale. L'impatto dipenderà dallo stato della macchina e può bloccarsi per lunghi periodi di tempo in alcuni casi.
L'uso di API di I/O asincrone e caricamento asincrono dei pacchetti deve assicurarsi che l'inizializzazione del pacchetto non blocchi il thread principale in questi casi. Gli utenti possono anche continuare a interagire con Visual Studio mentre le richieste di I/O vengono eseguite in background.
Inizializzazione anticipata di servizi, componenti
Uno dei modelli comuni nell'inizializzazione dei pacchetti consiste nell'inizializzare i servizi usati da o forniti da tale pacchetto nel pacchetto constructor
o initialize
nel metodo . Anche se ciò garantisce che i servizi siano pronti per l'uso, può anche aggiungere costi non necessari per il caricamento dei pacchetti se tali servizi non vengono usati immediatamente. Questi servizi devono invece essere inizializzati su richiesta per ridurre al minimo il lavoro svolto nell'inizializzazione del pacchetto.
Per i servizi globali forniti da un pacchetto, è possibile usare AddService
metodi che accettano una funzione per inizializzare in modo differizio il servizio solo quando viene richiesto da un componente. Per i servizi usati all'interno del pacchetto, è possibile usare Lazy<T> o AsyncLazy<T> per assicurarsi che i servizi vengano inizializzati/sottoposti a query al primo utilizzo.
Misurazione dell'impatto delle estensioni caricate automaticamente tramite il log attività
A partire da Visual Studio 2017 Update 3, il log attività di Visual Studio conterrà ora voci per l'impatto sulle prestazioni dei pacchetti durante l'avvio e il caricamento della soluzione. Per visualizzare queste misurazioni, è necessario aprire Visual Studio con l'opzione /log e aprire il file ActivityLog.xml .
Nel log attività le voci saranno incluse nell'origine "Gestisci prestazioni di Visual Studio" e avranno un aspetto simile all'esempio seguente:
Component: 3cd7f5bf-6662-4ff0-ade8-97b5ff12f39c, Inclusive Cost: 2008.9381, Exclusive Cost: 2008.9381, Top Level Inclusive Cost: 2008.9381
Questo esempio mostra che un pacchetto con GUID "3cd7f5bf-6662-4ff0-ade8-97b5ff12f39c" ha speso 2008 ms all'avvio di Visual Studio. Si noti che Visual Studio considera il costo di primo livello come numero primario quando si calcola l'impatto di un pacchetto, in quanto ciò sarebbe il risparmio visualizzato dagli utenti quando disabilitano l'estensione per tale pacchetto.
Misurazione dell'impatto delle estensioni caricate automaticamente con PerfView
Anche se l'analisi del codice consente di identificare i percorsi di codice che possono rallentare l'inizializzazione dei pacchetti, è anche possibile usare la traccia usando applicazioni come PerfView per comprendere l'impatto del caricamento di un pacchetto nell'avvio di Visual Studio.
PerfView è uno strumento di traccia a livello di sistema. Questo strumento consente di comprendere i percorsi ad accesso frequente in un'applicazione a causa dell'utilizzo della CPU o del blocco delle chiamate di sistema. Di seguito è riportato un rapido esempio sull'analisi di un'estensione di esempio con PerfView.
Codice di esempio:
Questo esempio è basato sul codice di esempio seguente, progettato per mostrare alcune cause comuni di ritardo:
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(); });
}
Registrazione di una traccia con PerfView:
Dopo aver configurato l'ambiente di Visual Studio con l'estensione installata, è possibile registrare una traccia di avvio aprendo PerfView e aprendo la finestra di dialogo Raccogli dal menu Raccogli .
Le opzioni predefinite forniranno stack di chiamate per l'utilizzo della CPU, ma poiché si è interessati anche a bloccare il tempo, è necessario abilitare anche gli stack di tempo di thread. Dopo aver pronto le impostazioni, è possibile fare clic su Avvia raccolta e quindi aprire Visual Studio dopo l'avvio della registrazione.
Prima di arrestare la raccolta, assicurarsi che Visual Studio sia completamente inizializzato, la finestra principale è completamente visibile e se l'estensione include parti dell'interfaccia utente visualizzate automaticamente, sono visibili anche. Quando Visual Studio viene caricato completamente e l'estensione viene inizializzata, è possibile arrestare la registrazione per analizzare la traccia.
Analisi di una traccia con PerfView:
Una volta completata la registrazione, PerfView aprirà automaticamente la traccia ed espanderà le opzioni.
Ai fini di questo esempio, si è interessati principalmente alla visualizzazione Stack di tempo di thread disponibile in Gruppo avanzato. Questa visualizzazione mostrerà il tempo totale dedicato a un thread da un metodo che include tempo cpu e tempo bloccato, ad esempio I/O su disco o in attesa di handle.
Durante l'apertura della visualizzazione Stack di tempo thread, è necessario scegliere il processo devenv per avviare l'analisi.
PerfView offre indicazioni dettagliate su come leggere gli stack di tempo del thread nel proprio menu ? per un'analisi più dettagliata. Ai fini di questo esempio, si vuole filtrare ulteriormente questa visualizzazione includendo solo gli stack con il nome del modulo dei pacchetti e il thread di avvio.
- Impostare GroupPats su testo vuoto per rimuovere qualsiasi raggruppamento aggiunto per impostazione predefinita.
- Impostare IncPats per includere parte del nome dell'assembly e del thread di avvio oltre al filtro di processo esistente. In questo caso, deve essere devenv; Thread di avvio; MakeVsSlowExtension.
La vista mostrerà ora solo i costi associati agli assembly correlati all'estensione. In questa visualizzazione, ogni volta elencato nella colonna Inc (costo inclusivo) del thread di avvio è correlato all'estensione filtrata e avrà un impatto sull'avvio.
Per l'esempio precedente alcuni stack di chiamate interessanti sono:
I/O usando
System.IO
la classe: anche se il costo inclusivo di questi fotogrammi potrebbe non essere troppo costoso nella traccia, è una potenziale causa di un problema poiché la velocità di I/O dei file varia da computer a computer.Blocco delle chiamate in attesa su un altro lavoro asincrono: in questo caso, il tempo inclusivo rappresenterebbe l'ora in cui il thread principale viene bloccato al completamento del lavoro asincrono.
Una delle altre viste nella traccia che sarà utile per determinare l'impatto sarà lo stack di caricamento delle immagini. È possibile applicare gli stessi filtri applicati alla visualizzazione Stack di tempo thread e individuare tutti gli assembly caricati a causa del codice eseguito dal pacchetto caricato automaticamente.
È importante ridurre al minimo il numero di assembly caricati all'interno di una routine di inizializzazione del pacchetto, perché ogni assembly aggiuntivo comporterà operazioni di I/O aggiuntive sul disco che possono rallentare notevolmente l'avvio su computer più lenti.
Riepilogo
L'avvio di Visual Studio è stata una delle aree su cui riceviamo continuamente commenti e suggerimenti. L'obiettivo, come indicato in precedenza, è che tutti gli utenti abbiano un'esperienza di avvio coerente indipendentemente dai componenti e dalle estensioni installati. Vorremmo collaborare con i proprietari delle estensioni per aiutarli a raggiungere tale obiettivo. Le indicazioni sopra riportate dovrebbero essere utili per comprendere l'impatto delle estensioni sull'avvio ed evitare la necessità di caricare automaticamente o caricarlo in modo asincrono per ridurre al minimo l'impatto sulla produttività degli utenti.