Condividi tramite


Guida per principianti all'ottimizzazione del codice e alla 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. In questo case study viene illustrato come usare gli strumenti di profilatura per eseguire questa attività.

L'obiettivo è fornire agli sviluppatori le conoscenze per:

  • Comprendere l'importanza dell'ottimizzazione del codice e il relativo impatto sulla riduzione dei costi di calcolo.
  • Usare gli strumenti di profilatura di Visual Studio per analizzare le prestazioni dell'applicazione.
  • Interpretare i dati forniti da questi strumenti per identificare i colli di bottiglia delle prestazioni.
  • Applicare strategie pratiche per ottimizzare il codice, concentrandosi sull'utilizzo della CPU, sull'allocazione della memoria e sulle interazioni del database.

Alla fine di questa guida, i lettori dovrebbero essere in grado di applicare queste tecniche ai propri progetti, portando a applicazioni più efficienti e convenienti.

Case study di ottimizzazione

L'applicazione di esempio descritta in questo case study è un'applicazione .NET progettata per eseguire query su un database di blog e post di blog associati. 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 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 soffre di un collo di bottiglia delle prestazioni comune che ne influisce significativamente sull'efficienza e, di conseguenza, i 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.

Esercizio

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:

  • 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.

Per la raccolta dei dati sono necessari i passaggi seguenti:

  • L'app viene impostata su una build di rilascio.
  • Selezionare lo strumento Utilizzo CPU dal profiler prestazioni (ALT+F2). I passaggi successivi coinvolgono alcuni degli altri strumenti.
  • Dal Profiler prestazioni si avvia l'app e si raccoglie 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 Le informazioni dettagliate principali e il percorso critico. Il percorso critico mostra il percorso del codice con il maggior utilizzo della CPU nell'app. Queste sezioni possono fornire suggerimenti utili per identificare rapidamente i problemi di prestazioni che è possibile migliorare.

È anche possibile visualizzare il percorso critico nella visualizzazione Albero delle chiamate. Per aprire questa visualizzazione, usare il collegamento Apri dettagli nel report e quindi selezionare Albero delle chiamate.

In questa visualizzazione viene visualizzato di nuovo il percorso critico, che mostra 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 dalla visualizzazione Albero delle chiamate per il collo di bottiglia reale.

Screenshot della visualizzazione Albero delle chiamate nello strumento Utilizzo CPU.

GetBlogTitleXeffettua 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 potrebbe essere necessario cercare una query LINQ come area da ottimizzare.

Screenshot della visualizzazione Albero delle chiamate nello strumento Utilizzo CPU con l'opzione Auto CPU evidenziata.

Per ottenere un albero delle chiamate visualizzato e una visualizzazione diversa dei dati, 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 .

Screenshot della visualizzazione Flame Graph nello strumento Utilizzo CPU.

Raccogliere dati aggiuntivi

Spesso, altri strumenti possono fornire informazioni aggiuntive per facilitare l'analisi e isolare il problema. Per questo esempio viene adottato l'approccio seguente:

  • Prima di tutto, si esamini 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.

Screenshot della visualizzazione Albero delle chiamate nello strumento Allocazione oggetti .NET.

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

Nel Profiler prestazioni selezionare lo strumento Database anziché l'utilizzo della CPU (oppure è possibile selezionare entrambi). Dopo aver raccolto una traccia, selezionare la scheda Query nella pagina 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.

Screenshot delle query di database nello strumento Database.

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, si espande 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 qui vengono recuperati molti valori di colonna, forse più di quanto 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 e ottenere questo codice.

foreach (var x in db.Posts.Where(p => p.Author.Contains("Fred Smith")).Select(b => b.Title).ToList())
{
  Console.WriteLine("Post: " + x);
}

In questo codice sono state apportate diverse modifiche per ottimizzare la query:

  • Aggiunta della Where clausola ed eliminazione di uno dei foreach 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%.

Screenshot del miglioramento dell'utilizzo della CPU nella visualizzazione Albero delle chiamate dello strumento Utilizzo CPU.

Si passa alla visualizzazione Flame Graph per visualizzare un'altra visualizzazione del miglioramento. In questa visualizzazione viene GetBlogTitleX usata anche una parte più piccola della CPU.

Screenshot del miglioramento dell'utilizzo della CPU nella visualizzazione Flame Graph dello strumento Utilizzo CPU.

I risultati vengono controllati nella traccia dello strumento database e vengono letti solo due record usando questa query, anziché 100.000. Inoltre, la query è molto semplificata ed elimina l'OPERAZIONE LEFT JOIN non necessaria generata in precedenza.

Screenshot del tempo di query più veloce nello strumento Database.

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!

Screenshot delle allocazioni di memoria ridotte nello strumento Allocazione oggetti .NET.

Iterare

Potrebbero essere necessarie più ottimizzazioni ed è possibile continuare a eseguire l'iterazione con le modifiche del codice per verificare quali modifiche migliorano le prestazioni e riducono 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.