Cenni preliminari sulla profilatura
Un profiler è un strumento che consente di monitorare l'esecuzione di un'altra applicazione. Un profiler di Common Language Runtime è una DLL costituita da funzioni che ricevono e inviano messaggi a CLR utilizzando l'API di analisi. La DLL del profiler viene caricata in fase di esecuzione da CLR.
Gli strumenti di analisi tradizionali si basano sulla misurazione dell'esecuzione dell'applicazione, ovvero del tempo impiegato da ciascuna funzione oppure dell'utilizzo della memoria nel tempo da parte dell'applicazione. L'API di analisi fa riferimento a una classe più ampia di strumenti di diagnostica, quali utilità di code coverage e persino supporti di debug avanzati, tutti utilizzati per fini diagnostici. L'API di analisi non si limita a misurare l'esecuzione di un'applicazione, ne esegue anche il monitoraggio. Per questa ragione, non deve mai essere utilizzata dall'applicazione stessa e l'esecuzione dell'applicazione non deve dipendere dal profiler (né essere influenzata da questo).
L'analisi di un'applicazione CLR richiede un maggiore supporto rispetto all'analisi del codice macchina compilato in modo tradizionale, in quanto CLR introduce concetti quali domini dell'applicazione, Garbage Collection, gestione delle eccezioni gestite, compilazione del codice JIT (tramite la conversione del codice MSIL in codice macchina nativo) e funzionalità simili. I meccanismi di analisi convenzionali non sono in grado di identificare o fornire informazioni utili su queste funzionalità. L'API di analisi fornisce queste informazioni mancanti in modo efficiente, con un impatto minimo sulle prestazioni di CLR e dell'applicazione profilata.
La compilazione JIT in fase di esecuzione offre buone possibilità di analisi. L'API di analisi consente a un profiler di modificare il flusso di codice MSIL in memoria per una routine prima che venga compilato tramite JIT. In tal modo, il profiler può aggiungere dinamicamente codice di strumentazione a routine specifiche per le quali è richiesta un'indagine più approfondita. Sebbene questo approccio sia possibile negli scenari convenzionali, è molto più semplice da implementare per CLR utilizzando l'API di analisi.
In questa panoramica sono incluse le sezioni seguenti:
API di analisi
Funzionalità supportate
Thread di notifica
Sicurezza
Combinazione di codice gestito e non gestito in un Code Profiler
Analisi del codice non gestito
Utilizzo di COM
Stack di chiamate
Callback e profondità dello stack
Argomenti correlati
API di analisi
In genere, l'API di analisi viene utilizzata per scrivere un Code Profiler, ovvero un programma che consente di monitorare l'esecuzione di un'applicazione gestita.
L'API di analisi viene utilizzata da una DLL del profiler caricata nello stesso processo dell'applicazione che si sta profilando. La DLL del profiler implementa un'interfaccia di callback (ICorProfilerCallback in .NET Framework versione 1.0 e 1.1, ICorProfilerCallback2 nella versione 2.0 e nelle versioni successive). CLR chiama i metodi in tale interfaccia per notificare al profiler gli eventi nel processo profilato. Il profiler può eseguire un callback nel runtime utilizzando i metodi delle interfacce ICorProfilerInfo e ICorProfilerInfo2 per ottenere informazioni sulla stato dell'applicazione profilata.
![]() |
---|
Solo la parte della soluzione del profiler relativa alla raccolta dei dati deve essere in esecuzione nello stesso processo dell'applicazione profilata.L'interfaccia utente e l'analisi dei dati devono essere eseguite in un processo separato. |
Nell'illustrazione seguente viene mostrata la modalità di interazione della DLL del profiler con l'applicazione che si sta profilando e con CLR.
Architettura di analisi
Interfacce di notifica
ICorProfilerCallback e ICorProfilerCallback2 possono essere considerate interfacce di notifica. Tali interfacce sono costituite da metodi quali ClassLoadStarted, ClassLoadFinishede JITCompilationStarted. Ogni volta che CLR carica o scarica una classe, compila una funzione e così via, chiama il metodo corrispondente nell'interfaccia ICorProfilerCallback o ICorProfilerCallback2 del profiler.
Ad esempio, un profiler potrebbe misurare le prestazioni del codice tramite due funzioni di notifica: FunctionEnter2 e FunctionLeave2. Il profiler imposta un timestamp per ogni notifica, accumula risultati e restituisce un elenco in cui sono indicate le funzioni che hanno utilizzato maggiormente la CPU o che hanno richiesto più tempo durante l'esecuzione dell'applicazione.
Interfacce di reperimento delle informazioni
Le altre interfacce principali coinvolte nell'analisi sono ICorProfilerInfo e ICorProfilerInfo2. Il profiler chiama queste interfacce secondo necessità per ottenere una maggiore quantità di informazioni per l'analisi. Ad esempio, ogni volta che CRL chiama la funzione FunctionEnter2, fornisce un identificatore della funzione. Il profiler può ottenere ulteriori informazioni su quella funzione chiamando il metodo ICorProfilerInfo2::GetFunctionInfo2 per scoprire la classe padre della funzione, il nome e così via.
Torna all'inizio
Funzionalità supportate
L'API di profilatura fornisce informazioni su diversi eventi e azioni che si verificano in Common Language Runtime. È possibile utilizzare tali informazioni per monitorare il funzionamento interno dei processi e analizzare le prestazioni dell'applicazione .NET Framework in uso.
L'API di analisi recupera le informazioni sulle azioni e sugli eventi che si verificano in CLR riportati di seguito:
Eventi di avvio e arresto di CLR.
Eventi di creazione e arresto di domini dell'applicazione.
Eventi di caricamento e scaricamento di assembly.
Eventi di caricamento e scaricamento di moduli.
Eventi di creazione e distruzione di vtable COM.
Eventi di compilazione JIT e di code pitching.
Eventi di caricamento e scaricamento di classi.
Eventi di creazione e distruzione di thread.
Eventi di entrata e uscita delle funzioni.
Eccezioni.
Transizioni tra l'esecuzione di codice gestito e non gestito.
Transizioni tra contesti di runtime diversi.
Informazioni sulle sospensioni del runtime.
Informazioni sull'heap della memoria del runtime e sull'attività di Garbage Collection.
L'API di analisi può essere chiamata da qualsiasi linguaggio compatibile con COM (non gestito).
Dal punto di vista del consumo di CPU e della memoria, l'API si rivela efficiente. La profilatura non comporta modifiche all'applicazione profilata di importanza tale da provocare risultati fuorvianti.
L'API di analisi è utile sia per i profiler di campionamento, sia per i profiler non di campionamento. Un profiler di campionamento controlla l'analisi a cicli macchina regolari, vale a dire, a intervalli di 5 millisecondi. Un profiler non di campionamento riceve le informazioni su un evento in modo sincrono, insieme al thread che causa l'evento.
Funzionalità non supportata
L'API di analisi non supporta le funzionalità descritte di seguito:
Codice non gestito, che deve essere profilato utilizzando metodi Win32 convenzionali. Tuttavia, il profiler di CLR include eventi di transizione che consentono di determinare i limiti fra codice gestito e non gestito.
Applicazioni automodificanti, che modificano il proprio codice per scopi quali la programmazione orientata agli aspetti.
Controllo dei limiti, in quanto l'API di profilatura non fornisce queste informazioni. CLR fornisce il supporto intrinseco per la verifica dei limiti di tutto il codice gestito.
Profilatura remota, non supportata per i motivi seguenti:
L'analisi remota estende il tempo di esecuzione. Quando si utilizzano le interfacce di profilatura, è necessario ridurre al minimo il tempo di esecuzione in modo da non influire eccessivamente sui risultati della profilatura, soprattutto durante il monitoraggio delle prestazioni dell'esecuzione. La profilatura remota non rappresenta tuttavia una limitazione quando le interfacce di profilatura vengono utilizzate per monitorare l'utilizzo della memoria o per ottenere informazioni in fase di esecuzione su stack frame, oggetti e così via.
Il Code Profiler di CLR deve registrare una o più interfacce di callback con il runtime sul computer locale su cui è in esecuzione l'applicazione profilata. In tal modo la capacità di creare un Code Profiler remoto viene limitata.
Analisi negli ambienti di produzione con requisiti di disponibilità elevata. L'API di analisi è stata creata per supportare la diagnostica in fase di sviluppo. Non è stata sottoposta ai test accurati richiesto per supportare gli ambienti di produzione.
Torna all'inizio
Thread di notifica
Nella maggior parte dei casi, il thread che genera un evento esegue anche notifiche. Tali notifiche (ad esempio, FunctionEnter e FunctionLeave) non devono necessariamente fornire il ThreadIDesplicito. Inoltre, il profiler potrebbe utilizzare la memoria locale del thread per archiviare e aggiornare i blocchi delle analisi anziché indicizzarli in un archivio globale, basato sul ThreadID del thread interessato.
Questi callback non sono serializzati. Gli utenti dovranno proteggere il codice creando strutture dei dati thread-safe e bloccando il codice del profiler dove necessario, per impedire l'accesso parallelo da più thread. Pertanto, in certi casi è possibile ricevere una sequenza insolita di callback. Ad esempio, si supponga che un'applicazione gestita generi due thread che eseguono codice identico. In questo caso, è possibile ricevere un evento ICorProfilerCallback::JITCompilationStarted per una funzione da un thread e un callback FunctionEnter dall'altro thread, prima di ricevere il callback ICorProfilerCallback::JITCompilationFinished. In questo caso, l'utente riceverà un callback FunctionEnter per una funzione che potrebbe non essere ancora stata completamente compilata tramite JIT.
Torna all'inizio
Sicurezza
Una DLL del profiler è una DLL non gestita che si esegue come parte del motore di Common Language Runtime. Di conseguenza, il codice nella DLL del profiler non è soggetto alle restrizioni di sicurezza dall'accesso di codice gestito. Le uniche limitazioni relative alla DLL del profiler sono quelle imposte dal sistema operativo all'utente che sta eseguendo l'applicazione profilata.
Per evitare problemi relativi alla sicurezza, gli autori del profiler devono adottare le precauzioni appropriate. Ad esempio, durante l'installazione, è necessario aggiungere la DLL di un profiler a un elenco di controllo di accesso, in modo da renderne impossibile la modifica da parte di un utente malintenzionato.
Torna all'inizio
Combinazione di codice gestito e non gestito in un Code Profiler
La scrittura non corretta di un profiler può provocare dei riferimenti circolari, che determinano un comportamento imprevedibile.
Una verifica dell'API di analisi di CLR potrebbe dare l'impressione che sia possibile scrivere un profiler contenente componenti gestiti e non gestiti che comunicano tra loro mediante l'interoperabilità COM o chiamate indirette.
Sebbene sia possibile dal punto di vista della progettazione, l'API di analisi non supporta i componenti gestiti. Un profiler CLR deve essere completamente non gestito. Tentativi di combinare codice gestito e codice non gestito in un profiler CLR possono provocare violazioni di accesso, errori di esecuzione del programma o deadlock. I componenti gestiti del profiler genererebbero eventi nei relativi componenti non gestiti, che a loro volta effettuerebbero chiamate ai componenti gestiti, dando origine a riferimenti circolari.
L'unico percorso in cui un profiler CLR può chiamare in modo sicuro il codice gestito è il corpo di un metodo in codice MSIL (Microsoft Intermediate Language). Prima che la compilazione JIT di una funzione sia terminata, il profiler può inserire le chiamate gestite nel corpo MSIL di un metodo e completare quindi la compilazione JIT (vedere il metodo ICorProfilerInfo::GetILFunctionBody ). Questa tecnica può essere utilizzata, con buoni risultati, per la strumentazione selettiva di codice gestito oppure per la raccolta di statistiche e dati sulle prestazioni relativi a JIT.
In alternativa, un Code Profiler può inserire hook nativi nel corpo MSIL di ogni funzione gestita che effettua chiamate nel codice non gestito. Questa tecnica può essere utilizzata per la strumentazione e il code coverage. Ad esempio, un Code Profiler potrebbe inserire degli hook di strumentazione dopo ogni blocco di codice MSIL per garantire l'esecuzione del blocco. La modifica del corpo MSIL di un metodo è un'operazione molto delicata ed è necessario prendere in considerazione molteplici fattori.
Torna all'inizio
Analisi del codice non gestito
L'API di analisi di Common Language Runtime fornisce il supporto minimo per l'analisi di codice non gestito. È disponibile la seguente funzionalità:
Enumerazione delle catene dello stack. Questa funzionalità consente a un Code Profiler di determinare il limite tra codice gestito e codice non gestito.
Determinazione della corrispondenza di una catena dello stack a un codice gestito o a un codice nativo.
In Framework .NET versioni 1.0 e 1.1, questi metodi sono disponibili tramite il sottoinsieme in-process dell'API di debug di Common Language Runtime. Sono definiti nel file CorDebug.idl e sono illustrati in Cenni preliminari sul debug CLR.
In .NET Framework 2.0 e versioni successive è possibile utilizzare il metodo ICorProfilerInfo2::DoStackSnapshot per questa funzionalità.
Torna all'inizio
Utilizzo di COM
Sebbene le interfacce di analisi vengano definite come le interfacce COM, Common Language Runtime non inizializza effettivamente COM per utilizzarle. In questo modo, infatti, si evita di dover impostare il modello di threading utilizzando la funzione CoInitialize prima che l'applicazione gestita abbia specificato il modello da utilizzare. Analogamente, il profiler non deve chiamare la funzione CoInitialize, perché potrebbe scegliere un modello di threading non compatibile con l'applicazione profilata e comprometterne l'esecuzione.
Torna all'inizio
Stack di chiamate
L'API di traccia fornisce due modalità per ottenere gli stack di chiamate: un metodo basato sullo snapshot dello stack, che consente la raccolta di tipo sparse degli stack di chiamate, e un metodo basato sullo shadow stack, che registra lo stack di chiamate in ogni istante.
Snapshot dello stack
Uno snapshot dello stack è una traccia dello stack di un thread in un istante di tempo. L'API di analisi supporta la tracciatura di funzioni gestite nello stack, ma lascia la tracciatura delle funzioni non gestite alla funzione di verifica del percorso chiamate nello stack del profiler.
Per ulteriori informazioni sulla programmazione del profiler per verificare il percorso chiamate negli stack gestiti, vedere il metodo ICorProfilerInfo2::DoStackSnapshot nell’ambito della presente documentazione e Profiler Stack Walking in the .NET Framework 2.0: Basics and Beyond in MSDN Library (informazioni in lingua inglese).
Shadow stack
Un utilizzo troppo frequente del metodo basato sullo snapshot può creare rapidamente un problema di prestazioni. Se si desidera eseguire frequenti tracce dello stack, il profiler deve compilare uno shadow stack utilizzando i callback di eccezione FunctionEnter2, FunctionLeave2, FunctionTailcall2e ICorProfilerCallback2. Lo shadow stack è sempre aggiornato e può essere rapidamente copiato in un’area di archiviazione quando è necessario uno snapshot dello stack.
Uno shadow stack può ottenere argomenti della funzione, valori restituiti e informazioni sulle creazioni di istanze generiche. Queste informazioni sono disponibili solo tramite lo shadow stack e possono essere ottenute quando il controllo viene passato a una funzione. Tuttavia, queste informazioni potrebbero non essere disponibili in un secondo momento, durante l’esecuzione della funzione.
Torna all'inizio
Callback e profondità dello stack
I callback del profiler possono essere generati in condizioni strettamente vincolate allo stack e un overflow dello stack nel callback di un profiler causerà l'uscita immediata dal processo. Come risposta ai callback, un profiler deve assicurarsi di utilizzare uno stack di dimensioni ridotte. Se il profiler è destinato all'utilizzo in processi affidabili in caso di overflow dello stack, dovrà evitare di causare un overflow dello stack.
Torna all'inizio
Argomenti correlati
Titolo |
Descrizione |
---|---|
Vengono descritti i miglioramenti e le modifiche per quanto riguarda la profilatura in .NET Framework 2.0 e versioni successive. |
|
Viene illustrato come inizializzare un profiler, impostare notifiche degli eventi ed eseguire la profilatura di un servizio Windows. |
|
Vengono illustrati i callback generati per il caricamento di domini dell'applicazione, assembly, moduli e classi. |
|
Viene illustrata la modalità di attivazione, di rilevamento e di blocco del processo di Garbage Collection. |
|
Viene illustrato il modo in cui è possibile tenere traccia degli oggetti spostati durante un Garbage Collection. |
|
Viene illustrato il modo in cui un profiler può utilizzare i metadati per ottenere informazioni sugli oggetti. |
|
Viene illustrato il modo in cui un profiler può monitorare eventi di eccezioni. |
|
Viene illustrato il modo in cui un profiler può controllare la generazione automatica e manuale del codice . |
|
Vengono descritti gli ID per le classi, i thread e i domini dell'applicazione passati da Common Language Runtime ai profiler. |
|
Vengono illustrati i valori restituiti da HRESULT, come allocare i buffer di ritorno per l'utilizzo da parte delle API di profilatura e l'utilizzo dei parametri di output facoltativi. |
|
Vengono descritte le interfacce non gestite utilizzate dall'API di analisi. |
|
Vengono descritte le funzioni statiche globali non gestite utilizzate dall'API di analisi. |
|
Vengono descritte le enumerazioni non gestite utilizzate dall'API di analisi. |
|
Vengono descritte le strutture non gestite utilizzate dall’API di analisi. |
Torna all'inizio