【问题标题】:Why is conditional debugging so slow?为什么条件调试这么慢?
【发布时间】:2014-07-10 23:59:01
【问题描述】:

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

例如,假设我们有以下代码。假设我们添加了一个条件断点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 上运行了第一个案例,看看是否有区别。这是结果

智能:

~9 秒到达断点

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

日食:

~9 秒到达断点

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

Netbeans:

~ 12 秒到达断点

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

所以 IntelliJ 和 Eclipse 差不多,但 Netbeans 慢得多。

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

【问题讨论】:

  • 虽然我不确定我会想象它与调试器如何接收和评估来自目标 JVM 的数据有关。我想它与 JConsole 和 VisualVM 使用 RMI 收集这些数据的方式非常相似。这与源代码中的条件绝对不属于同一级别,这使得它们很难比较,因为它们在做完全不同的事情。
  • 您使用的是什么 IDE?可能是 IntelliJ 吗?
  • @AlexR 是 IntelliJ,但我在 eclipse 中遇到了同样的问题。
  • 您使用什么工具进行调试?
  • 我最好的猜测是一个非条件断点,调试器毫无疑问地停止代码,而在有条件的情况下,必须在决定停止或继续之前评估该条件。正如@MarkW 所说,获取数据需要一些时间,因为要评估条件,调试器会在断点处停止,获取数据并评估它。

标签: java debugging


【解决方案1】:

我还没有实现 IDE、调试器或 JVM,所以我不能确定事情是否完全按照我将在这里解释的那样进行。

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

JVM 不支持条件断点,因此 IDE 使用“hack”来完成此功能。 IDE 只是添加了一个正常的断点。每次遇到断点时,IDE 都会在提醒用户之前评估表达式本身,如果评估结果为假,它会发送“继续”命令。

现在检查您的示例。在第二个示例中,JVM 只执行一次这样的调用。在第一个示例中,这是完成 100000 次。每次 JVM 调用调试器并等待,直到它解释条件并向 JVM 发送命令“继续”(正如您在调试代码时可以手动执行的那样)。显然100000>1,所以这个过程需要时间。

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

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

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

【讨论】:

  • 我现在很好奇.. 我即将在 Eclipse 中运行这个确切的示例。
  • 试试看。我很高兴知道结果。
  • 您所说的关于 Eclipse 的内容非常简洁。知道这是不是真的会很惊讶。 NetBeans 呢?
  • 如果调试器具有在遇到断点时执行固定命令序列的功能,则可以进行很好的比较。使用单个命令“继续”测量断点的时间,而不是评估条件并执行相同“继续”的断点。
  • @ADTC,我知道 Java 反编译器。我也直接流利地阅读java字节码。然而,条件注入不一定静态编译到.class 文件中。它可以在文件加载期间注入,例如由代理。
【解决方案2】:

条件断点依赖于条件的解释性 (!) 评估,只要遇到断点的位置就执行。

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

虽然编译表达式会产生机器指令(在 Java 中,至少在 JIT 编译之后),但解释表达式基于抽象语法树(一种数据结构),例如 Equals( Variable( "i" ), Literal( 10000 )) 和基于该数据的代码结构,获取值(“i”)并计算操作(“==”)。

【讨论】:

  • “不是以同样的方式完成的”。有什么不同吗?你能解释一下吗?
  • 请理解我的回答是试图回避技术细节,这会使它极度膨胀。进一步阅读可以通过谷歌搜索解释器、抽象语法树等获得。细节因调试器而异,例如 Java IDE 或 gdb 用于 C/C++ 等。
猜你喜欢
  • 1970-01-01
  • 2012-12-28
  • 1970-01-01
  • 1970-01-01
  • 2012-07-27
  • 1970-01-01
  • 2021-09-03
  • 2015-10-05
  • 1970-01-01
相关资源
最近更新 更多