ver - java debugger online
¿Por qué es tan lenta la depuración condicional? (2)
Los puntos de ruptura condicionales se basan en la evaluación interpretativa (!) De la condición, que se realizará cada vez que se alcance la ubicación del punto de ruptura.
Alcanzar un punto de interrupción es rápido: el flujo secuencial de ejecución se interrumpe, por ejemplo, al reemplazar la instrucción en esa ubicación por algún código que activa una interrupción. Pero la evaluación de la condición debe obtener los valores de las variables de la memoria y calcular el resultado, que no se realiza de la misma manera que la expresión evaluaría en el código compilado. Se espera una considerable desaceleración.
Mientras que una expresión compilada da como resultado instrucciones de máquina (en Java, al menos después de la compilación JIT), una expresión interpretada se basa en un árbol de sintaxis abstracta (una estructura de datos), como Equals( Variable( "i" ), Literal( 10000 ))
y el código que desciende sobre esa estructura de datos, recupera valores ("i") y calcula operaciones ("==").
He notado que cuando depuro con puntos de interrupción condicionales, la ejecución se ralentiza dramáticamente. He sabido esto por un tiempo, y ahora me gustaría entender por qué. ¿Qué sucede exactamente que hace que la ejecución sea tan lenta? Sé que se está agregando un condicional, pero si lo agrego yo mismo, no ralentizo la ejecución.
Por ejemplo, digamos que tenemos el siguiente código. Y digamos que agregamos un punto de interrupción condicional a=i
. Permite establecer el condicional a 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);
}
}
Ahora, por el contrario, escribamos el condicional nosotros mismos.
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);
}
}
¿Por qué el tiempo de ejecución de estos dos es tan dramáticamente diferente? ¿Por qué el primero es mucho más lento?
En caso de que se lo pregunte, estoy usando Oracle-JDK-8 en Linux (Ubuntu). Obtengo los mismos resultados con Eclipse e IntelliJ.
Resultados del experimento
Corrí el primer caso en múltiples IDE para ver si hay una diferencia. Aquí están los resultados
IntelliJ:
~ 9 segundos para alcanzar el punto de ruptura
~ 90 segundos para alcanzar el final (incluidos los 9 segundos iniciales)
Eclipse:
~ 9 segundos para alcanzar el punto de ruptura
~ 90 segundos para alcanzar el final (incluidos los 9 segundos iniciales)
Netbeans:
~ 12 segundos para alcanzar el punto de ruptura
~ 190 segundos para alcanzar el final (incluidos los 12 segundos iniciales)
Así que IntelliJ y Eclipse son casi iguales, pero Netbeans es mucho más lento.
El segundo ejemplo se ejecuta casi instantáneamente en todos los IDE, por lo que no hice un experimento. (Pero lo hice todos los tres para ver si alguno de ellos tenía un retraso, ninguno lo hizo).
No he implementado IDE, depurador o JVM, así que no puedo estar seguro de que las cosas vayan exactamente como explicaré aquí.
Pero. Cuando el código se ejecuta con el depurador, la JVM interpreta el código hasta que se encuentra en el punto de interrupción. Luego se detiene y llama al depurador (IDE).
Las JVM no admiten puntos de interrupción condicionales, por lo que los IDE usan un "pirateo" para lograr esta función. El IDE simplemente agrega un punto de interrupción normal. Cada vez que se alcanza un punto de interrupción, el IDE evalúa la expresión en sí misma antes de alertar al usuario, si la evaluación es falsa, envía el comando "continuar".
Ahora examina tus ejemplos. En el segundo ejemplo, JVM realiza dicha llamada solo una vez. En el primer ejemplo esto se hace 100000 veces. Cada vez que JVM llama al depurador y espera hasta que interprete la condición y envía al comando "continuar" de JVM (exactamente como puede hacerlo manualmente cuando está depurando su código). Obviamente 100000> 1, por lo que este proceso lleva tiempo.
EDITAR: los siguientes 2 párrafos se escribieron como una suposición no probada solamente. Los experimentos de OP demostraron que están equivocados. Sin embargo, no quiero eliminarlos por completo: interpretemos esto como un pensamiento teórico y una propuesta de mejora para el equipo de Eclipse.
En cuanto a IntelliJ vs Eclipse. De nuevo, esto es solo una suposición. Vi que IntelliJ funciona mucho más lento con puntos de interrupción condicionales. También sé que los puntos de interrupción condicionales en IntelliJ no admiten algunos elementos del lenguaje de programación Java (por ejemplo, clases internas anónimas). Puedo concluir que IntelliJ probablemente compila el código que escribes como condición de tu brekepoint usando un lenguaje que no sea java (por ejemplo groovy o algo así). Esto probablemente causa una degradación adicional del rendimiento.
También sé que eclipse no usa el compilador javac estándar, sino su propio compilador que tiene muchas características geniales. Puedo asumir que probablemente los puntos de interrupción condicionales en eclipase se compilan como parte de su código, es decir, en realidad, el compilador crea automáticamente código como su número de ejemplo 2. Si esto es correcto, dicho código se ejecutará casi tan rápido como el código que contiene la instrucción if
escrita manualmente.