Java에서 실제로 오류가 없을 때 throw / catch를 논리의 일부로 사용하는 것은 예외를 던지고 잡는 것이 비싸고 루프에서 여러 번 수행하는 것이 일반적으로 다른 것보다 훨씬 느리기 때문에 일반적으로 나쁜 생각입니다 (일부) 예외를 던지지 않는 제어 구조.
내 질문은 던지기 / 캐치 자체에서 발생하거나 Exception 객체를 만들 때 발생하는 비용입니까 (실행 스택을 포함하여 많은 런타임 정보를 얻으므로)?
다른 말로하면
Exception e = new Exception();
그러나 던지지 마십시오. 던지기 비용의 대부분입니까, 아니면 던지기 + 잡기가 비싼 것을 처리합니까?
try / catch 블록에 코드를 넣는 것이 해당 코드를 실행하는 비용에 추가되는지 묻지 않고 예외를 잡는 것이 비싼 부분인지 또는 예외를 만드는 (생성자를 호출하는 것) 비싼 부분인지를 묻습니다 .
이것을 요청하는 또 다른 방법은 예외 인스턴스 하나를 만들어 던졌다가 다시 잡는 경우 던질 때마다 새 예외를 만드는 것보다 훨씬 빠릅니다.
답변
만들기 예외 대상은 다른 일반 객체를 만드는 것보다 더 비싼 없습니다. 주요 비용은 fillInStackTrace
호출 스택을 안내하고 스택 추적을 작성하는 데 필요한 모든 정보 (클래스, 메소드 이름, 행 번호 등)를 수집하는 기본 메소드에 숨겨져 있습니다.
높은 예외 비용에 대한 신화는 대부분의 Throwable
생성자가 암시 적으로 호출 한다는 사실에서 비롯됩니다 fillInStackTrace
. 그러나 스택 추적없이 생성하는 생성자 가 하나 Throwable
있습니다. 인스턴스화하기가 매우 빠른 던지기를 만들 수 있습니다. 간단한 예외를 만드는 또 다른 방법은 재정의하는 것 fillInStackTrace
입니다.
이제 예외 를 던지는 것은 어떻습니까?
사실, 던져진 예외가되는 위치에 따라 달라 붙 잡았다 .
동일한 메소드에서 (또는 더 정확하게는 컨텍스트가 인라인으로 인해 여러 메소드를 포함 할 수 있기 때문에)보다 정확하게 잡히면 (물론 JIT 컴파일 후) throw
빠르고 간단 goto
합니다.
그러나 catch
블록이 스택의 깊숙한 곳에있는 경우 JVM은 스택 프레임을 풀어야하며, 이는 훨씬 더 오래 걸릴 수 있습니다. synchronized
풀리는 것은 제거 된 스택 프레임이 소유 한 모니터를 해제하는 것을 의미하기 때문에 블록이나 방법이 관련된 경우 훨씬 오래 걸립니다 .
적절한 벤치 마크를 통해 위의 내용을 확인할 수는 있지만, 모든 측면이 이미 HotSpot의 성능 엔지니어 Alexey Shipilev : The Extra Performance of Lil ‘Exception 게시물에서 완벽하게 다루어 졌기 때문에이를 수행 할 필요는 없습니다 .
답변
대부분의 Throwable
생성자 에서 첫 번째 작업은 대부분 의 비용이 드는 스택 추적 을 채우는 것 입니다.
그러나 스택 추적을 비활성화하는 플래그가있는 보호 생성자가 있습니다. 이 생성자 는 확장 할 때도 액세스 할 수 Exception
있습니다. 사용자 정의 예외 유형을 작성하면 스택 추적 작성을 피하고 적은 정보로도 성능을 향상시킬 수 있습니다.
일반적인 방법으로 모든 유형의 단일 예외를 작성하면 스택 추적을 채우는 오버 헤드없이 여러 번 다시 던질 수 있습니다. 그러나 스택 추적은 특정 인스턴스에서 발생한 위치가 아니라 생성 된 위치를 반영합니다.
현재 버전의 Java는 스택 추적 작성을 최적화하려고 시도합니다. 기본 코드는 스택 추적을 채우기 위해 호출되며, 추적은 더 가벼운 기본 구조로 추적을 기록합니다. 해당 자바 StackTraceElement
객체는 느리게 만이 기록에서 만든 getStackTrace()
, printStackTrace()
추적을 필요로하거나 다른 방법이라고합니다.
스택 트레이스 생성을 제거하면 다른 주요 비용으로 스로우와 캐치 사이에서 스택을 풀 수 있습니다. 예외가 포착되기 전에 발생하는 개입 프레임이 적을수록 더 빠릅니다.
예외적 인 경우에만 예외가 발생하도록 프로그램을 설계하고 이와 같은 최적화는 정당화하기 어렵습니다.
답변
여기 예외에 대한 좋은 글이 있습니다.
http://shipilev.net/blog/2014/exceptional-performance/
결론은 스택 트레이스 구성과 스택 풀림이 고가의 부품이라는 것입니다. 아래 코드는 1.7
스택 추적을 켜거나 끌 수 있는 기능을 활용 합니다. 그런 다음이를 사용하여 다른 시나리오에 어떤 비용이 드는지 확인할 수 있습니다.
다음은 객체 생성의 타이밍입니다. String
여기에 추가 했으므로 스택을 작성하지 않으면 JavaException
Object 및 a 생성에 거의 차이가 없음을 알 수 있습니다 String
. 스택 쓰기를 설정하면 차이가 극적으로 나타납니다. 즉, 최소 1 배 이상 느립니다.
Time to create million String objects: 41.41 (ms)
Time to create million JavaException objects with stack: 608.89 (ms)
Time to create million JavaException objects without stack: 43.50 (ms)
다음은 특정 깊이에서 백만 번 던지기까지 얼마나 오래 걸 렸는지 보여줍니다.
|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%)|
| 16| 1428| 243| 588 (%)|
| 15| 1763| 393| 449 (%)|
| 14| 1746| 390| 448 (%)|
| 13| 1703| 384| 443 (%)|
| 12| 1697| 391| 434 (%)|
| 11| 1707| 410| 416 (%)|
| 10| 1226| 197| 622 (%)|
| 9| 1242| 206| 603 (%)|
| 8| 1251| 207| 604 (%)|
| 7| 1213| 208| 583 (%)|
| 6| 1164| 206| 565 (%)|
| 5| 1134| 205| 553 (%)|
| 4| 1106| 203| 545 (%)|
| 3| 1043| 192| 543 (%)|
다음은 거의 확실하게 단순화 된 것입니다 …
스택 쓰기를 사용하여 16의 깊이를 취하면 객체 생성에 약 ~ 40 %의 시간이 걸리며 실제 스택 추적은이 중 대부분을 차지합니다. JavaException 객체 인스턴스화의 ~ 93 %는 스택 추적이 발생하기 때문입니다. 이것은이 경우 스택을 푸는 데 다른 시간의 50 %가 걸린다는 것을 의미합니다.
스택 추적을 끄면 객체 생성이 훨씬 작은 비율, 즉 20 %를 차지하고 스택 해제가 시간의 80 %를 차지합니다.
두 경우 모두 스택 해제는 전체 시간의 큰 부분을 차지합니다.
public class JavaException extends Exception {
JavaException(String reason, int mode) {
super(reason, null, false, false);
}
JavaException(String reason) {
super(reason);
}
public static void main(String[] args) {
int iterations = 1000000;
long create_time_with = 0;
long create_time_without = 0;
long create_string = 0;
for (int i = 0; i < iterations; i++) {
long start = System.nanoTime();
JavaException jex = new JavaException("testing");
long stop = System.nanoTime();
create_time_with += stop - start;
start = System.nanoTime();
JavaException jex2 = new JavaException("testing", 1);
stop = System.nanoTime();
create_time_without += stop - start;
start = System.nanoTime();
String str = new String("testing");
stop = System.nanoTime();
create_string += stop - start;
}
double interval_with = ((double)create_time_with)/1000000;
double interval_without = ((double)create_time_without)/1000000;
double interval_string = ((double)create_string)/1000000;
System.out.printf("Time to create %d String objects: %.2f (ms)\n", iterations, interval_string);
System.out.printf("Time to create %d JavaException objects with stack: %.2f (ms)\n", iterations, interval_with);
System.out.printf("Time to create %d JavaException objects without stack: %.2f (ms)\n", iterations, interval_without);
JavaException jex = new JavaException("testing");
int depth = 14;
int i = depth;
double[] with_stack = new double[20];
double[] without_stack = new double[20];
for(; i > 0 ; --i) {
without_stack[i] = jex.timerLoop(i, iterations, 0)/1000000;
with_stack[i] = jex.timerLoop(i, iterations, 1)/1000000;
}
i = depth;
System.out.printf("|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%%)|\n");
for(; i > 0 ; --i) {
double ratio = (with_stack[i] / (double) without_stack[i]) * 100;
System.out.printf("|%5d| %14.0f| %15.0f| %2.0f (%%)| \n", i + 2, with_stack[i] , without_stack[i], ratio);
//System.out.printf("%d\t%.2f (ms)\n", i, ratio);
}
}
private int thrower(int i, int mode) throws JavaException {
ExArg.time_start[i] = System.nanoTime();
if(mode == 0) { throw new JavaException("without stack", 1); }
throw new JavaException("with stack");
}
private int catcher1(int i, int mode) throws JavaException{
return this.stack_of_calls(i, mode);
}
private long timerLoop(int depth, int iterations, int mode) {
for (int i = 0; i < iterations; i++) {
try {
this.catcher1(depth, mode);
} catch (JavaException e) {
ExArg.time_accum[depth] += (System.nanoTime() - ExArg.time_start[depth]);
}
}
//long stop = System.nanoTime();
return ExArg.time_accum[depth];
}
private int bad_method14(int i, int mode) throws JavaException {
if(i > 0) { this.thrower(i, mode); }
return i;
}
private int bad_method13(int i, int mode) throws JavaException {
if(i == 13) { this.thrower(i, mode); }
return bad_method14(i,mode);
}
private int bad_method12(int i, int mode) throws JavaException{
if(i == 12) { this.thrower(i, mode); }
return bad_method13(i,mode);
}
private int bad_method11(int i, int mode) throws JavaException{
if(i == 11) { this.thrower(i, mode); }
return bad_method12(i,mode);
}
private int bad_method10(int i, int mode) throws JavaException{
if(i == 10) { this.thrower(i, mode); }
return bad_method11(i,mode);
}
private int bad_method9(int i, int mode) throws JavaException{
if(i == 9) { this.thrower(i, mode); }
return bad_method10(i,mode);
}
private int bad_method8(int i, int mode) throws JavaException{
if(i == 8) { this.thrower(i, mode); }
return bad_method9(i,mode);
}
private int bad_method7(int i, int mode) throws JavaException{
if(i == 7) { this.thrower(i, mode); }
return bad_method8(i,mode);
}
private int bad_method6(int i, int mode) throws JavaException{
if(i == 6) { this.thrower(i, mode); }
return bad_method7(i,mode);
}
private int bad_method5(int i, int mode) throws JavaException{
if(i == 5) { this.thrower(i, mode); }
return bad_method6(i,mode);
}
private int bad_method4(int i, int mode) throws JavaException{
if(i == 4) { this.thrower(i, mode); }
return bad_method5(i,mode);
}
protected int bad_method3(int i, int mode) throws JavaException{
if(i == 3) { this.thrower(i, mode); }
return bad_method4(i,mode);
}
private int bad_method2(int i, int mode) throws JavaException{
if(i == 2) { this.thrower(i, mode); }
return bad_method3(i,mode);
}
private int bad_method1(int i, int mode) throws JavaException{
if(i == 1) { this.thrower(i, mode); }
return bad_method2(i,mode);
}
private int stack_of_calls(int i, int mode) throws JavaException{
if(i == 0) { this.thrower(i, mode); }
return bad_method1(i,mode);
}
}
class ExArg {
public static long[] time_start;
public static long[] time_accum;
static {
time_start = new long[20];
time_accum = new long[20];
};
}
이 예제의 스택 프레임은 일반적으로 찾은 것보다 작습니다.
javap를 사용하여 바이트 코드를 엿볼 수 있습니다
javap -c -v -constants JavaException.class
즉, 이것은 방법 4에 대한 것입니다 …
protected int bad_method3(int, int) throws JavaException;
flags: ACC_PROTECTED
Code:
stack=3, locals=3, args_size=3
0: iload_1
1: iconst_3
2: if_icmpne 12
5: aload_0
6: iload_1
7: iload_2
8: invokespecial #6 // Method thrower:(II)I
11: pop
12: aload_0
13: iload_1
14: iload_2
15: invokespecial #17 // Method bad_method4:(II)I
18: ireturn
LineNumberTable:
line 63: 0
line 64: 12
StackMapTable: number_of_entries = 1
frame_type = 12 /* same */
Exceptions:
throws JavaException
답변
스택 추적으로 Exception
with를 만들 null
려면 throw
및 try-catch
블록을 만드는 데 시간이 걸립니다 . 그러나 스택 추적을 채우는 데 평균 5 배가 더 걸립니다 .
성능에 미치는 영향을 보여주기 위해 다음 벤치 마크를 만들었습니다. -Djava.compiler=NONE
컴파일러 최적화를 비활성화하기 위해 Run Configuration에 추가했습니다 . 스택 추적 작성의 영향을 측정 Exception
하기 위해 스택없는 생성자를 활용 하도록 클래스를 확장했습니다 .
class NoStackException extends Exception{
public NoStackException() {
super("",null,false,false);
}
}
벤치 마크 코드는 다음과 같습니다.
public class ExceptionBenchmark {
private static final int NUM_TRIES = 100000;
public static void main(String[] args) {
long throwCatchTime = 0, newExceptionTime = 0, newObjectTime = 0, noStackExceptionTime = 0;
for (int i = 0; i < 30; i++) {
throwCatchTime += throwCatchLoop();
newExceptionTime += newExceptionLoop();
newObjectTime += newObjectLoop();
noStackExceptionTime += newNoStackExceptionLoop();
}
System.out.println("throwCatchTime = " + throwCatchTime / 30);
System.out.println("newExceptionTime = " + newExceptionTime / 30);
System.out.println("newStringTime = " + newObjectTime / 30);
System.out.println("noStackExceptionTime = " + noStackExceptionTime / 30);
}
private static long throwCatchLoop() {
Exception ex = new Exception(); //Instantiated here
long start = System.currentTimeMillis();
for (int i = 0; i < NUM_TRIES; i++) {
try {
throw ex; //repeatedly thrown
} catch (Exception e) {
// do nothing
}
}
long stop = System.currentTimeMillis();
return stop - start;
}
private static long newExceptionLoop() {
long start = System.currentTimeMillis();
for (int i = 0; i < NUM_TRIES; i++) {
Exception e = new Exception();
}
long stop = System.currentTimeMillis();
return stop - start;
}
private static long newObjectLoop() {
long start = System.currentTimeMillis();
for (int i = 0; i < NUM_TRIES; i++) {
Object o = new Object();
}
long stop = System.currentTimeMillis();
return stop - start;
}
private static long newNoStackExceptionLoop() {
long start = System.currentTimeMillis();
for (int i = 0; i < NUM_TRIES; i++) {
NoStackException e = new NoStackException();
}
long stop = System.currentTimeMillis();
return stop - start;
}
}
산출:
throwCatchTime = 19
newExceptionTime = 77
newObjectTime = 3
noStackExceptionTime = 15
이것은 a를 만드는 것이 NoStackException
같은 것을 반복적으로 던지는 것만큼이나 비싸다는 것을 의미합니다 Exception
. 또한 Exception
스택 추적을 작성하고 채우는 데 약 4 배 더 오래 걸린다 는 것을 보여줍니다 .
답변
질문의이 부분은 …
이것을 요청하는 또 다른 방법은 예외 인스턴스 하나를 만들어 던졌다가 다시 잡는 경우 던질 때마다 새 예외를 만드는 것보다 훨씬 빠릅니다.
예외를 생성하고 어딘가에 캐싱하면 성능이 향상되는 것처럼 보입니다. 그렇습니다. 이미 만들어 졌기 때문에 객체 생성시 기록되는 스택을 끄는 것과 같습니다.
이것들은 내가 얻은 타이밍입니다.이 후주의 사항을 읽으십시오 …
|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%)|
| 16| 193| 251| 77 (%)|
| 15| 390| 406| 96 (%)|
| 14| 394| 401| 98 (%)|
| 13| 381| 385| 99 (%)|
| 12| 387| 370| 105 (%)|
| 11| 368| 376| 98 (%)|
| 10| 188| 192| 98 (%)|
| 9| 193| 195| 99 (%)|
| 8| 200| 188| 106 (%)|
| 7| 187| 184| 102 (%)|
| 6| 196| 200| 98 (%)|
| 5| 197| 193| 102 (%)|
| 4| 198| 190| 104 (%)|
| 3| 193| 183| 105 (%)|
물론 이것의 문제는 스택 추적이 이제 객체가 어디에서 발생했는지가 아니라 인스턴스를 생성했는지를 가리 킵니다.
답변
@AustinD의 대답을 출발점으로 사용하여 약간의 조정을했습니다. 하단에 코드.
하나의 Exception 인스턴스가 반복적으로 발생하는 경우를 추가하는 것 외에도 정확한 성능 결과를 얻을 수 있도록 컴파일러 최적화를 해제했습니다. 이 답변-Djava.compiler=NONE
에 따라 VM 인수에 추가 했습니다 . (이클립스에서 Run Configuration → Arguments를 편집 하여이 VM 인수를 설정하십시오)
결과 :
new Exception + throw/catch = 643.5
new Exception only = 510.7
throw/catch only = 115.2
new String (benchmark) = 669.8
따라서 예외를 만드는 것은 던지고 잡는 것보다 약 5 배가 소요됩니다. 컴파일러가 많은 비용을 최적화하지 못한다고 가정합니다.
비교를 위해 최적화를 비활성화하지 않은 동일한 테스트 실행은 다음과 같습니다.
new Exception + throw/catch = 382.6
new Exception only = 379.5
throw/catch only = 0.3
new String (benchmark) = 15.6
암호:
public class ExceptionPerformanceTest {
private static final int NUM_TRIES = 1000000;
public static void main(String[] args) {
double numIterations = 10;
long exceptionPlusCatchTime = 0, excepTime = 0, strTime = 0, throwTime = 0;
for (int i = 0; i < numIterations; i++) {
exceptionPlusCatchTime += exceptionPlusCatchBlock();
excepTime += createException();
throwTime += catchBlock();
strTime += createString();
}
System.out.println("new Exception + throw/catch = " + exceptionPlusCatchTime / numIterations);
System.out.println("new Exception only = " + excepTime / numIterations);
System.out.println("throw/catch only = " + throwTime / numIterations);
System.out.println("new String (benchmark) = " + strTime / numIterations);
}
private static long exceptionPlusCatchBlock() {
long start = System.currentTimeMillis();
for (int i = 0; i < NUM_TRIES; i++) {
try {
throw new Exception();
} catch (Exception e) {
// do nothing
}
}
long stop = System.currentTimeMillis();
return stop - start;
}
private static long createException() {
long start = System.currentTimeMillis();
for (int i = 0; i < NUM_TRIES; i++) {
Exception e = new Exception();
}
long stop = System.currentTimeMillis();
return stop - start;
}
private static long createString() {
long start = System.currentTimeMillis();
for (int i = 0; i < NUM_TRIES; i++) {
Object o = new String("" + i);
}
long stop = System.currentTimeMillis();
return stop - start;
}
private static long catchBlock() {
Exception ex = new Exception(); //Instantiated here
long start = System.currentTimeMillis();
for (int i = 0; i < NUM_TRIES; i++) {
try {
throw ex; //repeatedly thrown
} catch (Exception e) {
// do nothing
}
}
long stop = System.currentTimeMillis();
return stop - start;
}
}