java - todas - ¿Es costoso usar bloques try-catch incluso si nunca se lanza una excepción?
todas las excepciones en java (6)
Para comprender por qué no se pueden realizar las optimizaciones, es útil comprender los mecanismos subyacentes. El ejemplo más sucinto que pude encontrar se implementó en las macros C en: http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html
#include <stdio.h>
#include <setjmp.h>
#define TRY do{ jmp_buf ex_buf__; switch( setjmp(ex_buf__) ){ case 0: while(1){
#define CATCH(x) break; case x:
#define FINALLY break; } default:
#define ETRY } }while(0)
#define THROW(x) longjmp(ex_buf__, x)
Los compiladores a menudo tienen dificultades para determinar si un salto se puede localizar en X, Y y Z, por lo que se saltan las optimizaciones que no pueden garantizar que sean seguras, pero la implementación en sí es bastante ligera.
Sabemos que es costoso detectar excepciones. Pero, ¿es también costoso usar un bloque try-catch en Java incluso si nunca se lanza una excepción?
Encontré la pregunta / respuesta de desbordamiento de pila ¿Por qué los bloques de prueba son caros? , pero es para .NET .
Sí, como han dicho los demás, un bloque try
inhibe algunas optimizaciones en los {}
caracteres que lo rodean. En particular, el optimizador debe suponer que una excepción podría ocurrir en cualquier punto dentro del bloque, por lo que no hay garantía de que las sentencias se ejecuten.
Por ejemplo:
try {
int x = a + b * c * d;
other stuff;
}
catch (something) {
....
}
int y = a + b * c * d;
use y somehow;
Sin la try
, el valor calculado para asignar a x
podría guardar como una "subexpresión común" y reutilizar para asignar a y
. Pero debido a la try
no hay garantía de que alguna vez se haya evaluado la primera expresión, por lo que la expresión debe ser recalculada. Esto no suele ser un gran problema en el código de "línea recta", pero puede ser significativo en un bucle.
Sin embargo, debe tenerse en cuenta que esto se aplica ÚNICAMENTE al código JITCed. javac solo hace una pequeña cantidad de optimización, y no hay costo para el intérprete de bytecode para entrar / salir de un bloque try
. (No hay códigos de bytes generados para marcar los límites del bloque).
Y para bestsss:
public class TryFinally {
public static void main(String[] argv) throws Throwable {
try {
throw new Throwable();
}
finally {
System.out.println("Finally!");
}
}
}
Salida:
C:/JavaTools>java TryFinally
Finally!
Exception in thread "main" java.lang.Throwable
at TryFinally.main(TryFinally.java:4)
salida javap:
C:/JavaTools>javap -c TryFinally.class
Compiled from "TryFinally.java"
public class TryFinally {
public TryFinally();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Throwable;
Code:
0: new #2 // class java/lang/Throwable
3: dup
4: invokespecial #3 // Method java/lang/Throwable."<init>":()V
7: athrow
8: astore_1
9: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
12: ldc #5 // String Finally!
14: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
17: aload_1
18: athrow
Exception table:
from to target type
0 9 8 any
}
No "GOTO".
Sin embargo, otro microbenchmark ( source ).
Creé una prueba en la que mido la versión del código try-catch y no-try-catch basado en un porcentaje de excepción. El porcentaje del 10% significa que el 10% de los casos de prueba tenían división por cero casos. En una situación, es manejado por un bloque try-catch, en la otra por un operador condicional. Aquí está mi tabla de resultados:
OS: Windows 8 6.2 x64
JVM: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 23.25-b01
Percentage | Result (try/if, ns) 0% | 88/90 1% | 89/87 10% | 86/97 90% | 85/83
Lo cual dice que no hay una diferencia significativa entre ninguno de estos casos.
Vamos a medirlo, ¿de acuerdo?
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 < 100000000 && 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";
}
public static void main(String[] args) throws Exception {
Benchmark[] benchmarks = {
new Benchmark("try") {
@Override int run(int iterations) throws Throwable {
int x = 0;
for (int i = 0; i < iterations; i++) {
try {
x += i;
} catch (Exception e) {
e.printStackTrace();
}
}
return x;
}
}, new Benchmark("no try") {
@Override int run(int iterations) throws Throwable {
int x = 0;
for (int i = 0; i < iterations; i++) {
x += i;
}
return x;
}
}
};
for (Benchmark bm : benchmarks) {
System.out.println(bm);
}
}
}
En mi computadora, esto imprime algo como:
try 0.598 ns
no try 0.601 ns
Al menos en este ejemplo trivial, la declaración try no tuvo un impacto mensurable en el rendimiento. Siéntete libre de medir otros más complejos.
En términos generales, recomiendo no preocuparse por el costo de rendimiento de las construcciones de lenguaje hasta que tenga evidencia de un problema real de rendimiento en su código. O como lo put Donald Knuth: "la optimización prematura es la raíz de todo mal".
try
/ catch
puede tener algún impacto en el rendimiento. Esto se debe a que evita que JVM realice algunas optimizaciones. Joshua Bloch, en "Effective Java", dijo lo siguiente:
• Colocar código dentro de un bloque try-catch inhibe ciertas optimizaciones que las implementaciones modernas de JVM podrían realizar.
try
casi no tiene ningún gasto. En lugar de hacer el trabajo de configurar el try
en tiempo de ejecución, los metadatos del código se estructuran en tiempo de compilación de modo que cuando se lanza una excepción, ahora se realiza una operación relativamente costosa de subir la pila y ver si existen bloques de try
que podrían capte esta excepción Desde la perspectiva de un profano, try
también ser gratis. De hecho, está lanzando la excepción que le cuesta, pero a menos que esté lanzando cientos o miles de excepciones, aún no notará el costo.
try
tiene algunos costos menores asociados. Java no puede hacer algunas optimizaciones en el código en un bloque try
que de lo contrario lo haría. Por ejemplo, Java a menudo volverá a organizar las instrucciones en un método para que se ejecute más rápido, pero Java también debe garantizar que si se lanza una excepción, la ejecución del método se observa como si sus sentencias, tal como están escritas en el código fuente, en orden hasta alguna línea.
Porque en un bloque try
se puede lanzar una excepción (¡en cualquier línea del bloque try! Algunas excepciones se lanzan asíncronamente, como llamar a stop
en un subproceso (que está en desuso) e incluso además de que OutOfMemoryError puede ocurrir casi en cualquier parte) y sin embargo, puede capturarse y el código continuar ejecutándose después con el mismo método, es más difícil razonar sobre las optimizaciones que se pueden realizar, por lo que es menos probable que sucedan. (Alguien tendría que programar el compilador para hacerlos, razonar y garantizar la corrección, etc. Sería un gran dolor para algo que era ''excepcional''). Pero de nuevo, en la práctica no notarás cosas como esta.