Thread.getStackTrace()有多昂贵?

20
在日志系统中,每个日志输出都是由一个帮助类完成的,该帮助类有一个类似于这样的方法。
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;
}

这个运行起来有多昂贵?并且会对性能产生重大影响吗?

4个回答

21

现在在 JDK 9 和 10 中,你可以使用 StackWalker,这不会产生昂贵的调用。

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());
            }
        });
    }

10

是的,这个调用会有一些开销,但很可能你会做出类似这样的事情:

public static boolean DEBUG_ON = true; //change this before your production build
那么,
public void debug(String message){
  if(DEBUG_ON){
     //stack code here
  }

}
这样做会导致您在实际代码中不受影响。
即使对于异常,您也将在生产构建中抛出整个堆栈跟踪的异常。
请注意,如果您正在使用一个不错的日志子系统,它们可能已经基于日志级别执行某些操作(在我们的日志系统中,根据级别,debug() 基本上是一个无操作)。Log4j 和其他一些日志库有不同的处理方式。
最后,我想说:不要担心这个问题,除非它被证明是真正的性能问题。过早的优化是万恶之源 :)

2
可以使用 logger.isDebugEnabled() 来代替使用布尔标志。 - Inv3r53
1
更好的方法是使用预处理指令。 - Orentet
两者都有好处。对于静态最终布尔值,编译器将仅删除if()语句内的任何内容,因此它有点像预处理指令;) - Kylar
10
在任何时候,我的(Windows)系统都在运行大约50-80个程序、服务和设备驱动程序。如果它们中的每一个都使用比程序员稍微想一下性能更多20%的资源(CPU和内存),那么这将对我的硬件造成巨大的影响。在编写代码之前考虑性能不是万恶之源(贪爱金钱才是)。 - Lawrence Dol

10

看起来获取当前线程及其关联的ID并不昂贵,但获取当前线程及其堆栈跟踪是昂贵的。使用new throwable().getStackTrace()方法似乎比获取线程堆栈跟踪的方法快得多。

另外请注意:由于这只是一个main方法,所以此基准测试几乎没有堆栈深度,在服务器环境中将会更加严重。

基准测试结果:

简单循环花费2毫秒

获取当前线程需要10毫秒

获取堆栈跟踪需要29564毫秒

获取throwable堆栈跟踪需要19910毫秒

代码:

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");

根据源代码,我会说currentThread负责这个差异。 - njzk2
6
这个基准测试出了问题。javac/JVM会对其进行优化,以至于第一次和第二次循环完全被移除。另外,毫秒数不能在这里使用。 - Roman
9
“获取堆栈跟踪花费了29564毫秒”这个说法是荒谬的;测试代码表明它只花费了2900纳秒。此外,@Roman完全正确,优化会改变结果。 - gerardw

6
据我所知,使用Thread.getStackTrace()会有一些影响,尤其是在使用大堆栈时(例如在服务器端或J2EE情况下)。您可以尝试使用Throwable.getStackTrace()来获得更好的性能。
无论如何,定期调用这些函数(而不是在异常情况下调用)都会影响您的应用程序。

10
@SoftwareMonkey 实际上是这样的:Throwable.fillInStackTrace() 可以利用它知道正在检查调用该方法的线程的堆栈,而 Thread.getStackTrace() 必须是线程安全的,并且更加昂贵。请参见http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6375302。 - David Moles
@Lawrence:如果你使用Thread.currentThread().getStackTrace(),那么它应该使用相同的fillInStackTrace而不必担心线程安全问题,是吗? - chitresh
1
@chitresh:我不确定你在问什么。Thread.currentThread()已经产生了一些开销,如果我正确理解了@David的话,.getStackTrace()比Throwable上的相同方法的开销要高得多,因为线程对象本身必须保持线程安全以获取关联线程的堆栈,而Throwable在new Throwable()上填充堆栈跟踪时已经知道它是为调用线程服务的。 - Lawrence Dol
1
@DavidMoles发布的链接已经过时了;现在可以在https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6375302找到该页面。 - hacker1024
@hacker1024 谢谢你的更新。 - David Moles
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接