2010-02-27 20 views
14

In un sistema di registrazione, ogni uscita di registro è fatto da una classe di supporto con un metodo come questoQuanto è costoso Thread.getStackTrace()?

public void debug(String message) { 
    Logger logger = Logger.getLogger(getCallingClass()); 
    logger.debug(message); 
} 
... 
public Class getCallingClass() { 
/* 
Calls Thread.getStackTrace() and back traces until the class on the stack trace 
!= this.getClass(). 
*/ 
    return classFound; 
} 

Come costoso è questo a correre e potrebbe avere notevoli successi di performance?

risposta

8

sì, c'è un certo overhead a questa chiamata, ma in tutta likelyhood, si sta andando a fare qualcosa di simile:

public static boolean DEBUG_ON = true; //change this before your production build 

poi,

public void debug(String message){ 
    if(DEBUG_ON){ 
    //stack code here 
    } 

} 

che farà sì che di non prendere il colpo nel tuo codice reale.

Anche in questo caso, per eccezioni, è possibile lanciare un'eccezione Traccia stack intera nella build di produzione.

Si noti che se si utilizza un sottosistema di registrazione decente, probabilmente eseguiranno già qualcosa in base al livello di registrazione (nel nostro sistema di log, a seconda del livello, debug() è fondamentalmente un no-op). Log4j e altri hanno diversi modi di gestirlo.

Infine, direi: non preoccuparti fino a quando non si rivelerà un vero problema di prestazioni. Ottimizzazione prematura è la radice di tutto il male :)

+1

invece di utilizzare un flag booleano questa condizione potrebbe essere utilizzata logger.isDebugEnabled() – Inv3r53

+1

un modo migliore è utilizzare le direttive di pre-elaborazione. – Orentet

+0

Ci sono vantaggi per entrambi. con il booleano finale statico, il compilatore rimuoverà qualsiasi cosa all'interno dell'istruzione if(), quindi è un po 'come una direttiva preprocessore;) – Kylar

4

Da quello che ricordo, c'è un certo impatto nell'uso di Thread.getStackTrace() - specialmente con stack di grandi dimensioni (come quando si usano in situazioni lato server o J2EE). Potresti provare Throwable.getStackTrace() per prestazioni migliori.

In ogni caso, chiamare queste funzioni regolarmente (al contrario di fare così in una situazione di eccezione) avrà un impatto sulla tua app.

+0

Dubito che il nuovo 'Throwable(). GetStackTrace()' andrà a qualcosa di più che chiamare 'Thread.getStackTrace()' sotto il cofano. –

+6

@SoftwareMonkey effettivamente fa: Throwable.fillInStackTrace() può trarre vantaggio dal sapere che sta esaminando lo stack per lo stesso thread che sta chiamando il metodo, mentre Thread.getStackTrace() deve essere thread-safe ed è molto più costoso. Vedi http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6375302 –

+0

@David: Buono a sapersi, grazie. –

6

Sembra che ottenere il thread corrente (e l'ID associato) non sia costoso, ma ottenere il thread corrente e la sua traccia di stack. Il nuovo pattern throwable(). GetStackTrace() sembra essere molto più veloce del pattern di tracciamento dello stack del thread.

Inoltre, nota: questo benchmark non ha quasi nessuna profondità di stack poiché è solo un metodo principale, quindi in un ambiente server questa penalità sarà molto più pesante.

risultati benchmark:

semplice ciclo ha preso 2 ms

Come thread corrente sono voluti 10 ms

Ottenere stack trace ha preso 29564 ms

Come throwable stack trace preso 19910 ms

Codice:

int trials = 10_000_000; 

    long start = System.currentTimeMillis(); 

    long a = 1; 
    for (int i = 0; i < trials; i += 1) { 
     a += 1; 
    } 

    long duration = System.currentTimeMillis() - start; 
    System.out.println("Simple loop took " + duration + " ms"); 

    start = System.currentTimeMillis(); 

    a = 1; 
    for (int i = 0; i < trials; i += 1) { 
     a += 1; 
     Thread.currentThread().getId(); 
    } 

    duration = System.currentTimeMillis() - start; 
    System.out.println("Getting current thread took " + duration + " ms"); 

    start = System.currentTimeMillis(); 

    a = 1; 
    for (int i = 0; i < trials; i += 1) { 
     a += 1; 
     Thread.currentThread().getStackTrace(); 
    } 

    duration = System.currentTimeMillis() - start; 
    System.out.println("Getting stack trace took " + duration + " ms"); 

      start = System.currentTimeMillis(); 

    a = 1; 
    for (int i = 0; i < trials; i += 1) { 
     a += 1; 
     (new Throwable()).getStackTrace(); 
    } 

    duration = System.currentTimeMillis() - start; 
    System.out.println("Getting throwable stack trace took " + duration + " ms"); 
+0

in base al codice sorgente, direi che currentThread è responsabile della differenza – njzk2

+2

Questo benchmark è rotto. javac/JVM lo ottimizzerà nella misura in cui il primo e il secondo anello sono completamente rimossi. Inoltre, millis non può essere utilizzato qui. – Roman

+3

"Ottenere la traccia di stack ha richiesto 29564 ms" è un'asserzione assurda; il codice di test indica che ci sono voluti 2.900 ** nano * secondi. Inoltre @Roman è del tutto corretto sull'ottimizzazione dei risultati. – gerardw

0

Ora con JDK 9 & 10, è possibile utilizzare StalkWalker, che non è una chiamata costosa.

private void invoke006() { 
     var stack = StackWalker.getInstance(StackWalker.Option.SHOW_HIDDEN_FRAMES).walk((s) -> s.collect(Collectors.toList())); 
     stack.forEach(stackFrame -> { 
      if (stackFrame.getMethodName().equals("masterInvoker")) { 
       System.err.println("master called !!"); 
       System.err.println(StackWalker.getInstance().walk((s) -> s.collect(Collectors.toList())).get(0).getMethodName() + ", line: " + StackWalker.getInstance().walk((s) -> s.collect(Collectors.toList())).get(0).getLineNumber()); 
      } 
     }); 
    }