1

由于我的新工作,我在 Java 方面做了很多工作,现在我正在研究这些微小的细节。显然,Java 代码在某种程度上是关于异常的。我想知道:

调用堆栈是否对 try-catch 块的性能有很大影响?即我是否应该避免尝试调用一个函数的函数......并且太深了?

我读到 try-catch 块只会影响异常的性能。但是,它们冒泡有多远有关系吗?

4

4 回答 4

2

让我们测量一下,好吗?

package tools.bench;

import java.math.BigDecimal;

public abstract class Benchmark {

    final String name;

    public Benchmark(String name) {
        this.name = name;
    }

    abstract int run(int iterations) throws Throwable;

    private BigDecimal time() {
        try {
            int nextI = 1;
            int i;
            long duration;
            do {
                i = nextI;
                long start = System.nanoTime();
                run(i);
                duration = System.nanoTime() - start;
                nextI = (i << 1) | 1;
            } while (duration < 1000000000 && nextI > 0);
            return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String toString() {
        return name + "\t" + time() + " ns";
    }

    enum ExceptionStrategy {
        none {
            @Override void run() {
                // do nothing
            }
        },
        normal {
            @Override void run() {
                throw new RuntimeException();
            }
        },
        withoutStackTrace {
            @Override void run() {
                throw new RuntimeException() {
                    public synchronized Throwable fillInStackTrace() {
                        return this;
                    };
                };
            }
        };

        abstract void run();
    }

    private static Benchmark tryBenchmark(final int depth, final ExceptionStrategy strat) {
        return new Benchmark("try, depth = " + depth + ", " + strat) {
            @Override int run(int iterations) {
                int x = 0;
                for (int i = 1; i < iterations; i++) {
                    try {
                        x += recurseAndThrow(depth);
                    } catch (Exception e) {
                        x++;
                    }
                }
                return x;
            }

            private int recurseAndThrow(int i) {
                if (i > 0) {
                    return recurseAndThrow(i - 1) + 1;
                } else {
                    strat.run();
                    return 0;
                }
            }
        };
    }

    public static void main(String[] args) throws Exception {
        int[] depths = {1, 10, 100, 1000, 10000};
        for (int depth : depths) {
            for (ExceptionStrategy strat : ExceptionStrategy.values()) {
                System.out.println(tryBenchmark(depth, strat));
            }
        }
    }
}

在我的(相当过时的)笔记本上,打印:

try, depth = 1, none                           5.153 ns
try, depth = 1, normal                      3374.113 ns
try, depth = 1, withoutStackTrace            602.570 ns
try, depth = 10, none                         59.019 ns
try, depth = 10, normal                     9064.392 ns
try, depth = 10, withoutStackTrace          3528.987 ns
try, depth = 100, none                       604.828 ns
try, depth = 100, normal                   49387.143 ns
try, depth = 100, withoutStackTrace        27968.674 ns
try, depth = 1000, none                     5388.270 ns
try, depth = 1000, normal                 457158.668 ns
try, depth = 1000, withoutStackTrace      271881.336 ns
try, depth = 10000, none                   69793.242 ns
try, depth = 10000, normal               2895133.943 ns
try, depth = 10000, withoutStackTrace    2728533.381 ns

显然,具体结果会因您的硬件以及 JVM 实现和配置而异。但是,总体模式可能保持不变。

结论:

  • try 语句本身产生的开销可以忽略不计。
  • 抛出异常并展开调用堆栈会产生与堆栈大小(或要展开的堆栈数量)成线性关系的开销。
    • 对于实际应用程序的堆栈大小(假设有 100 个堆栈帧),该开销约为 50 微秒,即 0.00005 秒。
    • 通过在没有堆栈跟踪的情况下抛出异常可以在一定程度上减少这种开销

建议:

  • 不要担心 try 语句的性能。
  • 不要使用异常来表示频繁发生的情况(例如,每秒超过 1000 次)。
  • 否则,不要担心抛出异常的性能。
  • 此外,“过早的优化是万恶之源” ;-)
于 2013-09-05T07:20:30.990 回答
1

例外是昂贵的。使用时,会创建堆栈跟踪。如果您可以检查异常,请执行此操作。不要使用try..catch进行流量控制。当您无法检查/验证时,请使用try..catch;一个例子是做 IO 操作。

当我看到有很多 try..catch 块的代码时,我的第一反应是“这是一个糟糕的设计!”。

于 2013-09-04T12:04:57.570 回答
0

1.- 调用堆栈深度无需担心,Java Just In Time Compiler 将对您的代码进行优化,如方法内联,为您的代码提供最佳性能。

2.- 捕获块确实会影响性能,这是因为捕获异常可能意味着 JVM 内部的几种不同操作,例如遍历异常表、堆栈展开和常见陷阱(取消优化 JIT 的优化代码)。

3.- 作为一条建议,不要担心封装代码并最终导致几个方法调用,这会导致巨大的堆栈深度,JVM 将进行优化以获得最佳性能,当你的应用程序被编译和当它运行时,始终以代码可读性和良好的设计模式为目标,因为这将有助于使您的代码更易于维护和修改,以防某些性能修复必须启动。关于 catch 块,评估它的必要性抛出或捕获的异常,如果您正在捕获尝试捕获最常见的异常,这样您就可以避免巨大的异常表。

于 2013-09-05T05:49:14.983 回答
-1

在调用 .printStackTrace 或 .getStackTrace 之前,不会加载堆栈跟踪。如果您在 catch 块的开头放置一个断点,您会注意到 Exception 对象的堆栈跟踪为空。

于 2013-09-04T13:17:05.597 回答