为什么条件调试这么慢?

时间:2014-07-09 18:18:08

标签: java debugging

我注意到当我使用条件断点进行调试时,执行速度会大大降低。我已经了解了一段时间,现在想了解原因。究竟发生了什么导致执行速度如此之慢?我知道有条件被添加,但如果我自己添加条件,我不会减慢执行速度。

例如,假设我们有以下代码。我们假设我们添加一个条件断点a=i。让我们将条件设置为i == 10000。

public class Main {

    public static void main(String[] args) {
        int a = 0;

        for (int i = 0; i<100000; i++) {
                a = i;  //put breakpoint here (if i == 10000)
        }
        System.out.println("Done! a=" + a);
    }
}

现在让我们自己编写条件。

public class Main {

    public static void main(String[] args) {
        int a = 0;

        for (int i = 0; i<100000; i++) {
            if (i == 10000)
                a = i; //put a NON-conditional breakpoint here
            else a = i;
        }
        System.out.println("Done! a=" + a);
    }
}

为什么这两者的运行时间如此显着不同?为什么第一个这么慢?

万一你想知道,我在Linux(Ubuntu)上使用Oracle-JDK-8。 我用Eclipse和IntelliJ得到了相同的结果。


实验结果

我在多个IDE上运行了第一个案例,看看是否存在差异。结果如下

的IntelliJ:

〜9秒达到断点

〜90秒完成(包括最初的9秒)

蚀:

〜9秒达到断点

〜90秒完成(包括最初的9秒)

Netbeans的:

〜12秒达到断点

〜190秒完成(包括最初的12秒)

所以IntelliJ和Eclipse大致相同,但Netbeans要慢得多。

第二个例子几乎立即在所有IDE上运行,所以我没有做实验。 (但我确实把这三个都运行了,看看是否有任何延迟,没有一个延迟。)

2 个答案:

答案 0 :(得分:16)

我还没有实现IDE,调试器或JVM,所以我不能确定事情的进展与我在这里解释完全一样。

但是。当代码与调试器一起运行时,JVM会解释代码,直到遇到断点。然后它停止并调用调试器(IDE)。

JVM不支持条件断点,因此IDE使用&#34; hack&#34;完成此功能。 IDE只是添加一个普通断点。每次遇到断点时,IDE会在警告用户之前评估表达式本身,如果评估结果为false,则会发送&#34; continue&#34;命令。

现在检查一下你的例子。在第二个示例中,JVM仅执行一次此类调用。在第一个例子中,这完成了100000次。每次JVM调用调试器并等待它解释条件并发送到JVM命令&#34;继续&#34; (正如您在调试代码时手动完成的那样)。显然100000> 1,所以这个过程需要时间。

编辑:接下来的两个段落仅作为一个未经证明的假设。 OP的实验表明它们是错误的。但是我不想完全删除它们:让我们将其解释为Eclipse团队的理论思考和改进建议。

关于IntelliJ与Eclipse的关系。同样,这只是假设。我看到IntelliJ使用条件断点工作得慢得多。我也知道IntelliJ中的条件断点不支持java编程语言的某些元素(例如匿名内部类)。我可以得出结论,IntelliJ可能使用java之外的语言(例如groovy或其他东西)编译你编写的代码作为你的brekepoint的条件。这可能会导致性能下降。

我也知道eclipse不使用标准的javac编译器,但它自己的编译器有很多很酷的功能。我可以假设eclipase中的条件断点可能被编译为代码的一部分,即实际上编译器会自动创建类似于示例编号2的代码。如果这是正确的,则此类代码的运行速度几乎与包含手动编写的代码{{1声明。

答案 1 :(得分:8)

条件断点依赖于条件的解释性(!)评估,只要达到断点的位置就可以完成。

命中断点很快:执行的顺序流程被中断,例如,通过用触发中断的某些代码替换该位置处的指令。但是条件的评估必须从内存中获取变量的值并计算结果,这与表达式在编译代码中评估的方式不同。预计会出现相当大的放缓。

虽然编译表达式导致机器指令(在Java中,至少在JIT编译之后),但解释表达式基于抽象语法树(数据结构),例如Equals( Variable( "i" ), Literal( 10000 ))和下降的代码该数据结构,获取值(&#34; i&#34;)并计算操作(&#34; ==&#34;)。