Case study: Guida per principianti all'ottimizzazione del codice e riduzione dei costi di calcolo (C#, Visual Basic, C++, F#)
La riduzione del tempo di calcolo comporta la riduzione dei costi, in modo da poter ottimizzare il codice in modo da risparmiare denaro. Questo case study usa un'applicazione di esempio con problemi di prestazioni per illustrare come usare gli strumenti di profilatura per migliorare l'efficienza. Per confrontare gli strumenti di profilatura, vedere Quale strumento scegliere?
Questo case study illustra questi argomenti:
- Importanza dell'ottimizzazione del codice e dell'impatto sulla riduzione dei costi di calcolo.
- Come usare gli strumenti di profilatura di Visual Studio per analizzare le prestazioni dell'applicazione.
- Come interpretare i dati forniti da questi strumenti per identificare i colli di bottiglia delle prestazioni.
- Come applicare strategie pratiche per ottimizzare il codice, concentrandosi sull'utilizzo della CPU, sull'allocazione della memoria e sulle interazioni del database.
Seguire e quindi applicare queste tecniche alle proprie applicazioni per renderle più efficienti e convenienti.
Case study di ottimizzazione
L'applicazione di esempio esaminata in questo case study è un'applicazione .NET che esegue query su un database di blog e post di blog. Usa Entity Framework, un ORM (Object-Relational Mapping) comune per .NET, per interagire con un database locale SQLite. L'applicazione è strutturata per eseguire un numero elevato di query, simulando uno scenario reale in cui potrebbe essere necessaria un'applicazione .NET per gestire attività di recupero dati estese. L'applicazione di esempio è una versione modificata dell'esempio introduttivo di Entity Framework.
Il problema principale delle prestazioni con l'applicazione di esempio consiste nel modo in cui gestisce le risorse di calcolo e interagisce con il database. L'applicazione presenta un collo di bottiglia delle prestazioni che influisce significativamente sull'efficienza e, di conseguenza, sui costi di calcolo associati all'esecuzione. Il problema include i sintomi seguenti:
Utilizzo elevato della CPU: le applicazioni possono eseguire calcoli inefficienti o attività di elaborazione in modo da consumare inutilmente una grande quantità di risorse DELLA CPU. Ciò può causare tempi di risposta lenti e costi operativi aumentati.
Allocazione di memoria inefficiente: le applicazioni possono talvolta riscontrare problemi relativi all'utilizzo e all'allocazione della memoria. Nelle app .NET, la gestione inefficiente della memoria può comportare un aumento della Garbage Collection, che a sua volta può influire sulle prestazioni dell'applicazione.
Overhead di interazione del database: le applicazioni che eseguono un numero elevato di query su un database possono riscontrare colli di bottiglia correlati alle interazioni del database. Sono incluse query inefficienti, chiamate di database eccessive e un uso scarso delle funzionalità di Entity Framework, che possono compromettere le prestazioni.
Il case study mira a risolvere questi problemi usando gli strumenti di profilatura di Visual Studio per analizzare le prestazioni dell'applicazione. Comprendendo dove e come migliorare le prestazioni dell'applicazione, gli sviluppatori possono implementare ottimizzazioni per ridurre l'utilizzo della CPU, migliorare l'efficienza di allocazione della memoria, semplificare le interazioni del database e ottimizzare l'utilizzo delle risorse. L'obiettivo finale è migliorare le prestazioni complessive dell'applicazione, rendendolo più efficiente e conveniente per l'esecuzione.
Proposta
Risolvere i problemi di prestazioni nell'applicazione .NET di esempio presenta diverse problematiche. Queste sfide derivano dalla complessità della diagnosi dei colli di bottiglia delle prestazioni. Le sfide principali per risolvere i problemi descritti sono le seguenti:
Diagnosi dei colli di bottiglia delle prestazioni: una delle principali sfide consiste nell'identificare accuratamente le cause principali dei problemi di prestazioni. L'utilizzo elevato della CPU, l'allocazione di memoria inefficiente e i sovraccarichi di interazione del database possono avere più fattori che contribuiscono. Gli sviluppatori devono usare gli strumenti di profilatura in modo efficace per diagnosticare questi problemi, che richiedono una certa comprensione del funzionamento di questi strumenti e di come interpretare l'output.
Vincoli di conoscenza e risorse: infine, i team possono affrontare vincoli correlati a conoscenze, competenze e risorse. La profilatura e l'ottimizzazione di un'applicazione richiedono competenze ed esperienze specifiche e non tutti i team possono avere accesso immediato a queste risorse.
Affrontare queste sfide richiede un approccio strategico che combina un uso efficace di strumenti di profilatura, conoscenze tecniche e un'attenta pianificazione e test. Il case study mira a guidare gli sviluppatori attraverso questo processo, fornendo strategie e informazioni dettagliate per superare queste sfide e migliorare le prestazioni dell'applicazione.
Strategia
Ecco una panoramica generale dell'approccio in questo case study:
- Si avvia l'indagine prendendo una traccia dell'utilizzo della CPU. Lo strumento Utilizzo CPU di Visual Studio è spesso utile per avviare indagini sulle prestazioni e ottimizzare il codice per ridurre i costi.
- Successivamente, per ottenere informazioni dettagliate aggiuntive per isolare i problemi o migliorare le prestazioni, viene raccolta una traccia usando uno degli altri strumenti di profilatura. Ad esempio:
- Si esamini l'utilizzo della memoria. Per .NET si prova prima lo strumento allocazione oggetti .NET. Per .NET o C++, è possibile esaminare invece lo strumento Utilizzo memoria.
- Per ADO.NET o Entity Framework, è possibile usare lo strumento Database per esaminare query SQL, tempi di query precisi e altro ancora.
La raccolta dati richiede le attività seguenti:
- Impostazione dell'app su una build di rilascio.
- Selezione dello strumento Utilizzo CPU dal profiler prestazioni (ALT+F2). I passaggi successivi coinvolgono alcuni degli altri strumenti.
- Dal Profiler prestazioni avviare l'app e raccogliere una traccia.
Esaminare le aree di utilizzo elevato della CPU
Dopo aver raccolto una traccia con lo strumento Utilizzo CPU e averla caricata in Visual Studio, si controlla prima di tutto la pagina iniziale del report con estensione diagsession che mostra i dati riepilogati. Usare il collegamento Apri dettagli nel report.
Nella visualizzazione dei dettagli del report aprire la visualizzazione Albero delle chiamate. Il percorso del codice con il maggior utilizzo della CPU nell'app viene chiamato percorso critico. L'icona di fiamma del percorso attivo () può aiutare a identificare rapidamente i problemi di prestazioni che potrebbero essere migliorati.
Nella visualizzazione Albero delle chiamate è possibile visualizzare un utilizzo elevato della CPU per il GetBlogTitleX
metodo nell'app, usando circa una quota del 60% dell'utilizzo della CPU dell'app. Tuttavia, il valore self CPU per GetBlogTitleX
è basso, solo circa .10%. A differenza della CPU totale, il valore self CPU esclude il tempo impiegato in altre funzioni, quindi sappiamo di guardare più lontano l'albero delle chiamate per il collo di bottiglia reale.
GetBlogTitleX
effettua chiamate esterne a due DLL LINQ, che usano la maggior parte del tempo di CPU, come evidenziato dai valori auto CPU molto elevati. Questo è il primo indizio che una query LINQ potrebbe essere un'area da ottimizzare.
Per ottenere un albero delle chiamate visualizzato e una visualizzazione diversa dei dati, aprire la visualizzazione Flame Graph . In alternativa, fare clic con il pulsante destro del mouse GetBlogTitleX
e scegliere Visualizza in Flame Graph. Anche in questo caso, sembra che il GetBlogTitleX
metodo sia responsabile di un sacco di utilizzo della CPU dell'app (mostrato in giallo). Le chiamate esterne alle DLL LINQ vengono visualizzate sotto la GetBlogTitleX
casella e usano tutto il tempo di CPU per il metodo .
Raccogliere dati aggiuntivi
Spesso, altri strumenti possono fornire informazioni aggiuntive per facilitare l'analisi e isolare il problema. In questo case study viene adottato l'approccio seguente:
- Esaminare prima di tutto l'utilizzo della memoria. Potrebbe esserci una correlazione tra utilizzo elevato della CPU e utilizzo elevato della memoria, quindi può essere utile esaminare entrambi per isolare il problema.
- Poiché sono state identificate le DLL LINQ, verrà esaminato anche lo strumento Database.
Controllare l'utilizzo della memoria
Per vedere cosa sta accadendo con l'app in termini di utilizzo della memoria, viene raccolta una traccia usando lo strumento di allocazione di oggetti .NET (per C++, è possibile usare invece lo strumento Utilizzo memoria). La visualizzazione Albero delle chiamate nella traccia della memoria mostra il percorso critico e consente di identificare un'area di utilizzo elevato della memoria. Nessuna sorpresa a questo punto, il GetBlogTitleX
metodo sembra generare un sacco di oggetti! Oltre 900.000 allocazioni di oggetti, infatti.
La maggior parte degli oggetti creati sono stringhe, matrici di oggetti e Int32s. È possibile vedere come questi tipi vengono generati esaminando il codice sorgente.
Controllare la query nello strumento Database
In Performance Profiler selezionare lo strumento Database anziché l'utilizzo della CPU (o selezionare entrambi). Dopo aver raccolto una traccia, aprire la scheda Query nella pagina di diagnostica. Nella scheda Query per la traccia database è possibile visualizzare la prima riga che mostra la query più lunga, 2446 ms. La colonna Record mostra il numero di record letti dalla query. È possibile usare queste informazioni per un confronto successivo.
Esaminando l'istruzione SELECT
generata da LINQ nella colonna Query, viene identificata la prima riga come query associata al GetBlogTitleX
metodo . Per visualizzare la stringa di query completa, espandere la larghezza della colonna. La stringa di query completa è:
SELECT "b"."Url", "b"."BlogId", "p"."PostId", "p"."Author", "p"."BlogId", "p"."Content", "p"."Date", "p"."MetaData", "p"."Title"
FROM "Blogs" AS "b" LEFT JOIN "Posts" AS "p" ON "b"."BlogId" = "p"."BlogId" ORDER BY "b"."BlogId"
Si noti che l'app sta recuperando molti valori di colonna qui, forse più di quanto sia necessario. Si esaminerà ora il codice sorgente.
Ottimizza codice
È il momento di esaminare il GetBlogTitleX
codice sorgente. Nello strumento Database fare clic con il pulsante destro del mouse sulla query e scegliere Vai al file di origine. Nel codice sorgente per GetBlogTitleX
è disponibile il codice seguente che usa LINQ per leggere il database.
foreach (var blog in db.Blogs.Select(b => new { b.Url, b.Posts }).ToList())
{
foreach (var post in blog.Posts)
{
if (post.Author == "Fred Smith")
{
Console.WriteLine($"Post: {post.Title}");
}
}
}
Questo codice usa foreach
cicli per cercare nel database tutti i blog con "Fred Smith" come autore. Esaminandolo, è possibile notare che molti oggetti vengono generati in memoria: una nuova matrice di oggetti per ogni blog nel database, stringhe associate per ogni URL e valori per le proprietà contenute nei post, ad esempio l'ID blog.
Vengono eseguite alcune ricerche e vengono fornite alcune raccomandazioni comuni per ottimizzare le query LINQ. In alternativa, possiamo risparmiare tempo e lasciare che Copilot faccia la ricerca per noi.
Se si usa Copilot, selezionare Chiedi a Copilot dal menu di scelta rapida e digitare la domanda seguente:
Can you make the LINQ query in this method faster?
Suggerimento
È possibile usare i comandi barra, ad esempio /optimize , per creare domande valide per Copilot.
In questo esempio Copilot fornisce le modifiche di codice suggerite seguenti, insieme a una spiegazione.
public void GetBlogTitleX()
{
var posts = db.Posts
.Where(post => post.Author == "Fred Smith")
.Select(post => post.Title)
.ToList();
foreach (var postTitle in posts)
{
Console.WriteLine($"Post: {postTitle}");
}
}
Questo codice include diverse modifiche per ottimizzare la query:
- Aggiunta della
Where
clausola ed eliminazione di uno deiforeach
cicli. - Proiettata solo la proprietà Title nell'istruzione
Select
, che è tutto ciò che è necessario in questo esempio.
Successivamente, si esegue il nuovo test usando gli strumenti di profilatura.
Risultati
Dopo aver aggiornato il codice, viene eseguito di nuovo lo strumento Utilizzo CPU per raccogliere una traccia. La visualizzazione Albero delle chiamate mostra che GetBlogTitleX
è in esecuzione solo 1754 ms, usando il 37% del totale della CPU dell'app, un miglioramento significativo del 59%.
Passare alla visualizzazione Flame Graph per visualizzare un'altra visualizzazione che mostra il miglioramento. In questa visualizzazione viene GetBlogTitleX
usata anche una parte più piccola della CPU.
Controllare i risultati nella traccia dello strumento database e solo due record vengono letti usando questa query, anziché 100.000. Inoltre, la query è molto semplificata ed elimina l'OPERAZIONE LEFT JOIN non necessaria generata in precedenza.
Successivamente, ricontrollare i risultati nello strumento allocazione oggetti .NET e si noterà che GetBlogTitleX
è responsabile solo di 56.000 allocazioni di oggetti, quasi un 95% di riduzione rispetto al 900.000!
Iterare
Potrebbero essere necessarie più ottimizzazioni ed è possibile continuare a eseguire l'iterazione con le modifiche del codice per vedere quali modifiche migliorano le prestazioni e consentono di ridurre i costi di calcolo.
Passaggi successivi
Gli articoli e i post di blog seguenti forniscono altre informazioni per imparare a usare in modo efficace gli strumenti per le prestazioni di Visual Studio.
- Case study: Isolare un problema di prestazioni
- Case study: Prestazioni doppie in meno di 30 minuti
- Miglioramento delle prestazioni di Visual Studio con il nuovo strumento di strumentazione