Si potrebbe usare un profiler, ma perché non fermare il programma?

Se qualcosa sta facendo un programma a thread singolo, prendi, diciamo, 10 volte il tempo necessario, potresti eseguire un profiler su di esso. Puoi anche fermarlo con un pulsante “pausa” e vedrai esattamente cosa sta facendo.

Anche se è solo il 10% più lento di quanto dovrebbe essere, se lo fermi più volte, tra non molto lo vedrai fare ripetutamente la cosa inutile. Di solito il problema è una chiamata di funzione da qualche parte nel mezzo dello stack che non è realmente necessaria. Questo non misura il problema, ma sicuramente lo trova.

Modifica: le obiezioni per lo più presuppongono che tu prenda solo 1 campione. Se sei serio, prendi 10. Qualsiasi linea di codice che causa una percentuale di spreco, come il 40%, apparirà sullo stack in quella frazione di campioni, in media. I colli di bottiglia (nel codice a thread singolo) non possono nasconderli.

EDIT: Per mostrare cosa intendo, molte obiezioni hanno la forma “non ci sono abbastanza campioni, quindi quello che vedi potrebbe essere completamente spurio” – idee vaghe sul caso. Ma se qualcosa di qualsiasi descrizione riconoscibile , non solo di essere in una routine o in cui la routine è triggers, è triggers per il 30% delle volte, quindi la probabilità di vederlo su un dato campione è del 30%.

Quindi supponiamo che vengano presi solo 10 campioni. Il numero di volte in cui il problema verrà visto in 10 campioni segue una distribuzione binomiale e la probabilità di vederlo 0 volte è 0,028. La probabilità di vederlo 1 volta è .121. Per 2 volte, la probabilità è 0,233, e per 3 volte è 0,267, dopo di che cade. Dal momento che la probabilità di vederlo meno di due volte è .028 + .121 = .139, ciò significa che la probabilità di vederlo due o più volte è 1 – .139 = .861. La regola generale è che se vedi qualcosa che potresti correggere su due o più campioni, vale la pena aggiustarlo.

In questo caso, la possibilità di vederlo in 10 campioni è dell’86%. Se sei del 14% che non lo vede, prenda più campioni finché non lo fai. (Se il numero di campioni è aumentato a 20, la possibilità di vederlo due o più volte aumenta a più del 99%.) Quindi non è stato misurato con precisione, ma è stato trovato con precisione, ed è importante capire che potrebbe facilmente essere qualcosa che un profiler non potrebbe effettivamente trovare, ad esempio qualcosa che riguarda lo stato dei dati, non il contatore del programma.

Sui server Java è sempre stato un trucco ordinario fare 2-3 veloci CtrlInterrompe in una fila e ottenere 2-3 threaddump di tutti i thread in esecuzione. Semplicemente guardando dove tutti i thread “sono” può individuare in modo estremamente rapido dove sono i tuoi problemi di prestazioni.

Questa tecnica può rivelare più problemi di prestazioni in 2 minuti rispetto a qualsiasi altra tecnica che conosco.

Perché a volte funziona, ea volte ti dà risposte completamente sbagliate. Un profiler ha una registrazione molto migliore di trovare la risposta giusta, e di solito arriva più velocemente.

Farlo manualmente non può essere chiamato “veloce” o “efficace”, ma ci sono diversi strumenti di profilazione che lo fanno automaticamente; noto anche come analisi statistica .

Il campionamento del callstack è una tecnica molto utile per la creazione di profili, specialmente quando si guarda a un codebase ampio e complicato che potrebbe passare il tempo in un numero qualsiasi di luoghi. Ha il vantaggio di misurare l’utilizzo della CPU in base all’orario wall-clock, che è ciò che conta per l’interattività, e ottenere casse di chiamata con ogni campione consente di vedere il motivo per cui viene chiamata una funzione. Lo uso spesso , ma utilizzo strumenti automatizzati, come Luke Stackwalker e OProfile e varie cose fornite dal fornitore di hardware.

Il motivo per cui preferisco gli strumenti automatici rispetto al campionamento manuale per il lavoro che faccio è il potere statistico . Afferrare dieci campioni a mano va bene quando hai una funzione che occupa il 40% del tempo di esecuzione, perché in media avrai quattro campioni e sempre almeno uno. Ma hai bisogno di più campioni quando hai un profilo piatto, con centinaia di funzioni foglia, nessuno che richiede più dell’1,5% del runtime.

Supponi di avere un lago con molti tipi diversi di pesci. Se il 40% del pesce nel lago è salmone (e il 60% “tutto il resto”), allora devi solo catturare dieci pesci per sapere che c’è molto salmone nel lago. Ma se hai centinaia di diverse specie di pesci, e ogni specie è individualmente non più dell’1%, dovrai catturare molto più di dieci pesci per poter dire “questo lago è salmone allo 0,8% e trota allo 0,6%” “.

Allo stesso modo nei giochi su cui lavoro, ci sono diversi sistemi principali, ognuno dei quali chiama dozzine di funzioni in centinaia di quadro diverse, e tutto ciò accade 60 volte al secondo. Alcune di queste funzioni funzionano in operazioni comuni (come malloc ), ma la maggior parte non lo fa, e in ogni caso non c’è una singola foglia che occupa più di 1000 μs per frame.

Posso osservare le funzioni del trunk e vedere “stiamo spendendo il 10% del nostro tempo in caso di collisione”, ma non è molto utile: ho bisogno di sapere esattamente dove si trova in collisione, quindi so quali funzioni spremere. Solo “fare meno collisione” ti porta solo lontano, specialmente quando significa buttare fuori le caratteristiche. Preferirei sapere “stiamo spendendo una media di 600 μs / frame sui miss della cache nella fase ristretta dell’ottree perché il missile magico si muove così velocemente e tocca molte celle”, perché allora posso rintracciare la correzione esatta: o un albero migliore o missili più lenti.

Il campionamento manuale andrebbe bene se ci fosse un grosso stricmp 20%, diciamo, in stricmp , ma con i nostri profili non è così. Invece ho centinaia di funzioni che devo ottenere, per esempio, dallo 0,6% del frame allo 0,4% del frame. Ho bisogno di radere 10 μs su ogni funzione da 50 μs che viene chiamata 300 volte al secondo. Per ottenere quel tipo di precisione, ho bisogno di più campioni.

Ma in fondo quello che fa Luke Stackwalker è quello che descrivi: ogni millisecondo o giù di lì, interrompe il programma e registra il callstack (comprese le istruzioni precise e il numero di riga dell’IP ). Alcuni programmi richiedono solo decine di migliaia di campioni per essere profilati utilmente.

(Ne abbiamo parlato prima, naturalmente, ma ho pensato che fosse un buon posto per riassumere il dibattito).

C’è una differenza tra le cose che i programmatori effettivamente fanno e le cose che raccomandano agli altri.

Conosco molti programmatori (me compreso) che usano effettivamente questo metodo. Aiuta davvero a trovare il più ovvio dei problemi di prestazioni, ma è veloce e sporco e funziona.

Ma non direi davvero agli altri programmatori di farlo, perché mi richiederebbe troppo tempo per spiegare tutti gli avvertimenti. È fin troppo facile fare una conclusione imprecisa basata su questo metodo e ci sono molte aree in cui semplicemente non funziona affatto. (ad esempio, quel metodo non rivela alcun codice che viene triggersto dall’input dell’utente).

Quindi, proprio come usare i rilevatori di bugie in tribunale, o la frase “goto”, non è consigliabile che tu lo faccia, anche se tutti hanno i loro usi.

Sono sorpreso dal tono religioso di entrambe le parti.

Il profiling è fantastico e sicuramente è più raffinato e preciso quando puoi farlo. A volte non puoi, ed è bello avere un affidabile back-up. La tecnica di pausa è come il cacciavite manuale che si usa quando il tuo elettroutensile è troppo lontano o le battaglie si sono esaurite.

Ecco una breve storia vera. Un’applicazione (una specie di task di elaborazione batch) funzionava bene in produzione da sei mesi, improvvisamente gli operatori chiamano sviluppatori perché stanno andando “troppo lenti”. Non ci permetteranno di albind un profiler di campionamento in produzione! Devi lavorare con gli strumenti già installati. Senza interrompere il processo di produzione, utilizzando semplicemente Process Explorer , (che gli operatori avevano già installato sulla macchina) potremmo vedere un’istantanea dello stack di un thread. Puoi dare un’occhiata in cima alla pila, chiuderla con il tasto Invio e ottenere un’altra istantanea con un altro clic del mouse. Puoi facilmente ottenere un campione ogni secondo o così.

Non ci vuole molto per vedere se la parte superiore della pila è più spesso nella DLL del database client della libreria (in attesa sul database), o in un’altra DLL di sistema (in attesa di un’operazione di sistema), o in realtà in qualche metodo del applicazione stessa. In questo caso, se ricordo bene, abbiamo subito notato che 8 volte su 10 l’applicazione era in un file DLL di sistema chiamata a leggere o scrivere un file di rete. Sicuramente abbastanza recenti “aggiornamenti” avevano cambiato le caratteristiche di prestazione di una condivisione di file. Senza un approccio rapido e sporco e (l’amministratore del sistema autorizzato) per vedere cosa stava facendo l’applicazione in produzione , avremmo impiegato molto più tempo a provare a misurare il problema, piuttosto che a correggere il problema.

D’altra parte, quando i requisiti di prestazione vanno oltre “abbastanza bene” per spingere davvero la busta, un profiler diventa essenziale in modo che tu possa provare a radere i cicli da tutti i tuoi top-ten o venti hot spot strettamente legati. Anche se stai cercando di soddisfare un requisito di prestazioni moderato durante un progetto, quando riesci a trovare gli strumenti giusti allineati per aiutarti a misurare e testare e persino a integrarli nel tuo processo di test automatico, può essere estremamente utile.

Ma quando il potere è fuori (si fa per dire) e le batterie sono morte, è bello sapere come usare quel cacciavite manuale.

Quindi la risposta diretta: sai cosa puoi imparare dall’arrestare il programma, ma non aver paura degli strumenti di precisione. La cosa più importante è sapere quali lavori richiedono gli strumenti.

Se prendiamo la domanda “Perché non è più noto?” allora la risposta sarà soggettiva. Presumibilmente il motivo per cui non è più noto è perché la profilazione fornisce una soluzione a lungo termine piuttosto che una soluzione di problemi attuali. Non è efficace per le applicazioni multi-thread e non è efficace per applicazioni come i giochi che trascorrono una parte significativa del tempo di rendering.

Inoltre, nelle applicazioni a thread singolo se si dispone di un metodo che si prevede di consumare più tempo di esecuzione e si desidera ridurre il tempo di esecuzione di tutti gli altri metodi, sarà più difficile determinare quali metodi secondari per concentrare i propri sforzi prima

Il tuo processo di profilazione è un metodo accettabile che può funzionare e funziona, ma la creazione di profili ti fornisce più informazioni e ha il vantaggio di mostrarti miglioramenti e regressioni più dettagliati delle prestazioni.

Se si dispone di un codice ben strumentato, è ansible esaminare più di un metodo particolare; puoi vedere tutti i metodi.

Con la profilazione:

  • È quindi ansible rieseguire lo scenario dopo ogni modifica per determinare il grado di miglioramento / regressione delle prestazioni.

  • È ansible profilare il codice su diverse configurazioni hardware per determinare se l’hardware di produzione sarà sufficiente.

  • È ansible definire il profilo del codice in scenari di test di carico e stress per determinare in che modo il volume di informazioni influisce sulle prestazioni

  • Puoi rendere più semplice per gli sviluppatori junior visualizzare gli impatti delle loro modifiche al tuo codice, in quanto possono riprogrammare il codice in sei mesi mentre sei in spiaggia o al pub o in entrambi. Beach-pub, ftw.

Il profilo viene dato più peso perché il codice aziendale deve sempre avere un certo grado di profilazione a causa dei vantaggi che offre all’organizzazione di un lungo periodo di tempo. Più importante è il codice, più profilazione e test fai.

Il tuo approccio è valido e un altro elemento è la cassetta degli attrezzi dello sviluppatore. Viene superato solo dalla profilazione.

Il pulsante di pausa durante l’esecuzione di un programma in modalità “debug” potrebbe non fornire i dati corretti per eseguire ottimizzazioni delle prestazioni. Per dirla senza mezzi termini, è una forma grezza di profilazione.

Se devi evitare di usare un profiler, una scommessa migliore è usare un logger, e quindi applicare un fattore di rallentamento a “guesstimate” dove il vero problema è. Tuttavia, i profilatori sono strumenti migliori per l’ipotesi.

Il motivo per cui premendo il pulsante di pausa in modalità di debug, potrebbe non dare un’immagine reale del comportamento dell’applicazione è perché i debugger introducono codice eseguibile aggiuntivo che può rallentare alcune parti dell’applicazione. Si può fare riferimento al post sul blog di Mike Stall sui possibili motivi del rallentamento dell’applicazione in un ambiente di debug. Il post chiarisce alcuni motivi, come troppi punti di interruzione, creazione di oggetti di eccezione, codice non ottimizzato, ecc. La parte relativa al codice non ottimizzato è importante: la modalità di “debug” risulterà in molte ottimizzazioni (di solito codifica in-line e ri- ordine) che viene espulso dalla finestra, per abilitare l’host di debug (il processo che esegue il codice) e l’IDE per sincronizzare l’esecuzione del codice. Pertanto, premere ripetutamente la pausa in modalità “debug” potrebbe essere una ctriggers idea.

I profiler di campionamento sono utili solo quando

  1. Stai monitorando un runtime con un piccolo numero di thread. Preferibilmente uno
  2. La profondità dello stack di chiamata di ogni thread è relativamente piccola (per ridurre l’incredibile overhead nella raccolta di un campione).
  3. Sei preoccupato solo del tempo di orologio da parete e non di altri contatori o colli di bottiglia delle risorse.
  4. Non hai strumentato il codice per scopi di gestione e monitoraggio (da qui le richieste di dumping dello stack)
  5. Ritenete erroneamente che la rimozione di uno stack frame sia un’efficace strategia di miglioramento delle prestazioni, a prescindere dal fatto che i costi intrinseci (esclusi i calle) siano praticamente pari a zero o meno
  6. Non si può essere infastiditi nell’apprendere come applicare quotidianamente l’ingegneria delle prestazioni del software nel proprio lavoro
  7. ….

Le istantanee di traccia stack consentono solo di vedere i raggi X stroboscopici dell’applicazione. Potresti aver bisogno di più conoscenze accumulate che un profiler potrebbe darti.

Il trucco sta nel conoscere bene i tuoi strumenti e scegliere il meglio per il lavoro da svolgere.

Questi devono essere alcuni esempi banali con cui stai lavorando per ottenere risultati utili con il tuo metodo. Non riesco a pensare a un progetto in cui la profilazione fosse utile (con qualsiasi metodo) che avrebbe ottenuto risultati decenti con il tuo metodo “rapido ed efficace”. Il tempo necessario per avviare e interrompere alcune applicazioni mette già in discussione la tua affermazione di “veloce”.

Di nuovo, con programmi non banali il metodo che difendi è inutile.

EDIT: Per quanto riguarda “perché non è meglio conosciuto”?

Nella mia esperienza, le revisioni del codice evitano codice e algoritmi di scarsa qualità, e il profiling potrebbe trovare anche questi. Se si desidera continuare con il metodo che è bello, ma penso che per la maggior parte della comunità professionale questo è così in basso nella lista delle cose da provare che non otterrà mai un rinforzo positivo come un buon uso del tempo.

Sembra essere piuttosto impreciso con insiemi di campioni di piccole dimensioni e ottenere serie di campioni di grandi dimensioni richiederebbe molto tempo che sarebbe stato meglio speso con altre attività utili.

Cosa succede se il programma è in produzione e viene utilizzato contemporaneamente pagando clienti o colleghi. Un profiler ti permette di osservare senza interferire (tanto, perché ovviamente avrà anche un piccolo successo secondo il principio di Heisenberg ).

La creazione di profili può anche fornire rapporti accurati più ricchi e dettagliati. Questo sarà più veloce a lungo termine.

EDIT 2008/11/25: OK, la risposta di Vineet mi ha fatto finalmente vedere qual è il problema qui. Meglio tardi che mai.

In qualche modo l’idea si è scatenata sulla terra e sono stati rilevati problemi di prestazioni misurando le prestazioni. Questo è confuso significa con fini. In qualche modo l’ho evitato con programmi singoli di stepping singolo molto tempo fa. Non mi sono rimproverato per averlo rallentato a velocità umana. Stavo cercando di vedere se stava facendo cose sbagliate o inutili. Ecco come rendere il software veloce: trova e rimuovi le operazioni non necessarie.

Nessuno ha la pazienza di fare stepping single in questi giorni, ma la cosa migliore da fare è scegliere un numero di cicli a caso e chiedere quali sono le loro ragioni. (Questo è quello che lo stack di chiamate può spesso dirti.) Se una buona percentuale di loro non ha buone ragioni, puoi fare qualcosa al riguardo.

Al giorno d’oggi è più difficile, con threading e asincronia, ma è così che sintonizzo il software – trovando cicli inutili. Non vedendo quanto sia veloce – lo faccio alla fine.


Ecco perché il campionamento dello stack di chiamate non può dare una risposta sbagliata, e perché non sono necessari molti campioni.

Durante l’intervallo di interesse, quando il programma impiega più tempo di quanto desideri, lo stack di chiamate esiste continuamente, anche quando non lo stai campionando.

  • Se un’istruzione I si trova nello stack di chiamate per la frazione P (I) di quel tempo, rimuovendola dal programma, se ansible, salverebbe esattamente così tanto. Se questo non è ovvio, dagli un po ‘di pensiero.

Se l’istruzione si presenta su M = 2 o più campioni, su N, la sua P (I) è approssimativamente M / N, ed è decisamente significativa.

L’unico modo in cui non riesci a vedere l’istruzione è quello di tempo magicamente tutti i tuoi campioni per quando l’istruzione non è nello stack delle chiamate. Il semplice fatto che sia presente per una frazione del tempo è ciò che lo espone alle tue sonde.

Quindi il processo di ottimizzazione delle prestazioni è una semplice questione di selezionare le istruzioni (per lo più istruzioni di chiamata di funzione) che sollevano la testa accedendo a più campioni dello stack di chiamate. Quelli sono gli alti alberi nella foresta.

Nota che non dobbiamo preoccuparci del grafico delle chiamate, di quanto tempo impiegano le funzioni, o di quante volte sono chiamate, o della ricorsione.

Sono contro l’offuscamento, non contro i profiler. They give you lots of statistics, but most don’t give P(I), and most users don’t realize that that’s what matters.

You can talk about forests and trees, but for any performance problem that you can fix by modifying code, you need to modify instructions, specifically instructions with high P(I). So you need to know where those are, preferably without playing Sherlock Holmes. Stack sampling tells you exactly where they are.

This technique is harder to employ in multi-thread, event-driven, or systems in production. That’s where profilers, if they would report P(I), could really help.

Stepping through code is great for seeing the nitty-gritty details and troubleshooting algorithms. It’s like looking at a tree really up close and following each vein of bark and branch individually.

Profiling lets you see the big picture, and quickly identify trouble points — like taking a step backwards and looking at the whole forest and noticing the tallest trees. By sorting your function calls by length of execution time, you can quickly identify the areas that are the trouble points.

I used this method for Commodore 64 BASIC many years ago. It is surprising how well it works.

I’ve typically used it on real-time programs that were overrunning their timeslice. You can’t manually stop and restart code that has to run 60 times every second.

I’ve also used it to track down the bottleneck in a compiler I had written. You wouldn’t want to try to break such a program manually, because you really have no way of knowing if you are breaking at the spot where the bottlenck is, or just at the spot after the bottleneck when the OS is allowed back in to stop it. Also, what if the major bottleneck is something you can’t do anything about, but you’d like to get rid of all the other largeish bottlenecks in the system? How to you prioritize which bottlenecks to attack first, when you don’t have good data on where they all are, and what their relative impact each is?

The larger your program gets, the more useful a profiler will be. If you need to optimize a program which contains thousands of conditional branches, a profiler can be indispensible. Feed in your largest sample of test data, and when it’s done import the profiling data into Excel. Then you check your assumptions about likely hot spots against the actual data. There are always surprises.