예외가 발생하지 않더라도 try-catch 블록을 사용하는 것이 비용이 많이 듭니까?


189

우리는 예외를 잡는 것이 비싸다는 것을 알고 있습니다. 그러나 예외가 발생하지 않더라도 Java에서 try-catch 블록을 사용하는 것도 비용이 많이 듭니까?

스택 오버플로 질문 / 응답을 찾았습니다. try 블록은 왜 비쌉니까? 그러나 .NET 용 입니다.


30
이 질문에는 아무런 의미가 없습니다. Try..catch는 매우 구체적인 목적을 가지고 있습니다. 필요한 경우 필요합니다. 어쨌든 캐치가없는 시도는 어떤 점입니까?
JohnFx

49
try { /* do stuff */ } finally { /* make sure to release resources */ }합법적이고 유용합니다
A4L

4
그 비용은 이점과 비교되어야합니다. 혼자 서 있지 않습니다. 어쨌든 비싸다는 것은 상대적이며, 할 수 없다는 것을 알 때까지는 한 시간 동안 밀리 초 또는 두 시간을 절약 할 수 있기 때문에 무언가를하지 않고 가장 확실한 방법을 사용하는 것이 좋습니다. 프로그램 실행.
Joel

4
나는 이것이 "재발 명 오류 코드"유형의 상황으로
이끌지 않기를 바란다

6
@SAFX는 : Java7에 당신도 제거 할 수 finally사용 A 동try-with-resources
a_horse_with_no_name

답변:


201

try거의 비용이 들지 않습니다. try런타임시 설정 작업을 수행하는 대신 , 코드의 메타 데이터는 컴파일 타임에 구성되어 예외가 발생했을 때 스택을 올라가고이를 try포착 할 수 있는 블록이 있는지 비교적 비싼 작업을 수행 합니다. 예외. 평신도의 관점에서 볼 try때 무료 일 수도 있습니다. 실제로 비용이 드는 예외를 던지고 있습니다. 그러나 수백 또는 수천 개의 예외를 던지지 않는 한 여전히 비용을 눈치 채지 못할 것입니다.


try관련 비용이 약간 있습니다. Java try는 그렇지 않은 블록의 코드에서 일부 최적화를 수행 할 수 없습니다 . 예를 들어, Java는 더 빠른 실행을 위해 메소드에서 명령을 재배 열하는 경우가 종종 있습니다. 그러나 Java는 예외가 발생하면 메소드의 실행이 소스 코드에서 작성된 것처럼 해당 명령문이 실행 된 것으로 간주됨을 보장해야합니다. 몇 줄까지.

try블록에서 예외가 발생할 수 있기 때문에 (try 블록의 모든 라인에서! stop쓰레드 를 호출 하는 등의 일부 예외는 (더 이상 사용되지 않음) OutOfMemoryError는 거의 모든 곳에서 발생할 수 있음) 비동기 적으로 발생 합니다. 같은 방법으로 코드가 계속해서 실행되고 나면, 최적화가 가능한지에 대해 추론하기가 어려워서 발생 가능성이 줄어 듭니다. (누군가 컴파일러를 프로그래밍하고, 정확성을 추론하고 보장하는 등의 작업을해야합니다. '예외적'이라는 것은 큰 고통이 될 것입니다.) 그러나 실제로는 이와 같은 것을 눈치 채지 못할 것입니다.


2
일부 예외는 비동기식으로 발생하며 비동기식 은 아니지만 안전한 지점에서 발생합니다. 이 부분 은 약간의 비용이 듭니다. Java는 try 블록의 코드에서 일부 최적화를 수행 할 수 없으며, 그렇지 않으면 심각한 참조가 필요합니다. 어떤 시점에서 코드는 try / catch 블록 내에있을 가능성이 높습니다. try / catch 블록이 인라인되고 결과에 대해 적절한 격자를 만들기가 더 어려울 수 있지만 재 배열 된 부분이 모호합니다.
bestsss

2
않는 try...finally없이 블록 catch도 약간의 최적화를 방지?
dajood

5
@Patashu "실제로 비용이 드는 예외를 던지고 있습니다" 기술적으로 예외를 던지는 것은 비싸지 않습니다. Exception객체를 인스턴스화하는 데 대부분의 시간이 걸립니다.
Austin D

72

측정 해 봅시다.

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);
        }
    }
}

내 컴퓨터에서 다음과 같이 인쇄됩니다.

try     0.598 ns
no try  0.601 ns

최소한이 간단한 예에서 try 문은 성능에 측정 가능한 영향을 미치지 않았습니다. 더 복잡한 것을 자유롭게 측정하십시오.

일반적으로 코드에 실제 성능 문제가 있다는 증거가 나타날 때까지 언어 구문의 성능 비용에 대해 걱정하지 않는 것이 좋습니다. 또는 도널드 크 누스 (Donald Knuth) "조기 최적화는 모든 악의 근원"이라고 말합니다.


4
try / no try는 대부분의 JVM에서 동일 할 가능성이 높지만, 마이크로 벤치 마크에는 심각한 결함이 있습니다.
bestsss 2013 년

2
꽤 몇 가지 수준 : 결과가 1ns 미만으로 계산된다는 의미입니까? 컴파일 된 코드는 try / catch와 루프를 모두 제거합니다 (1에서 n까지의 숫자의 합은 사소한 산술 진행 합계입니다). 코드에 try / finally가 포함되어 있어도 컴파일러는이를 입증 할 수 있습니다. 추상 코드에는 2 개의 호출 사이트 만 있으며 복제 및 인라인됩니다. 더 많은 경우가 있습니다. 마이크로 벤치 마크에서 기사를 찾아보고 마이크로 벤치 마크를 작성하기로 결정하면 항상 생성 된 어셈블리를 확인하십시오.
bestsss

3
보고 된 시간은 루프 반복마다 입니다. 총 경과 시간이 0.1 초 이상인 경우에만 측정이 사용되므로 (또는 여기에 해당되지 않은 20 억 반복) 루프가 완전히 제거하기 어렵다고 주장합니다. 루프가 제거되었습니다. 실행하는 데 0.1 초가 걸렸습니까?
meriton

...에 따르면 실제로 -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly루프와 그 안에 추가 된 것은 생성 된 네이티브 코드에 존재합니다. 그리고 아니, 추상 메소드는 호출자가 시간 컴파일되지 않았기 때문에 (아마도 충분한 시간이 호출되지 않았기 때문에) 인라인되지 않습니다.
meriton

Java로 올바른 마이크로 벤치 마크를 작성하는 방법 : stackoverflow.com/questions/504103/…
Vadzim

47

try/ catch성능에 영향을 줄 수 있습니다. JVM이 일부 최적화를 수행하지 못하게하기 때문입니다. "유효한 Java"의 Joshua Bloch는 다음과 같이 말했습니다.

• try-catch 블록 안에 코드를 배치하면 최신 JVM 구현이 수행 할 수있는 특정 최적화가 금지됩니다.


25
"JVM이 일부 최적화를 수행하지 못하게합니다"...? 좀 더 자세히 설명해 주시겠습니까?
크라켄

5
예를 들어, try 블록 내부의 Kraken 코드 (보통? 항상?)는 try 블록 외부의 코드로 재정렬 할 수 없습니다.
Patashu

3
문제는 "성능에 영향을 미치는지"가 아니라 "비싸다"라는 점에 주목하십시오.
mikołak

3
Effective Java에서 발췌 한 내용을 추가했습니다 . 물론 그것은 java의 성경입니다. 참조가 없으면 발췌에서 아무 말도하지 않습니다. 실제로 Java의 모든 코드는 try / finally 내에 있습니다.
bestsss 2013 년

29

그러나 다른 사람들이 말했듯이 try블록은 {}주변 의 캐릭터 에 대한 일부 최적화를 금지 합니다. 특히, 옵티마이 저는 블록 내의 어느 시점에서나 예외가 발생할 수 있다고 가정해야하므로 명령문이 실행된다는 보장이 없습니다.

예를 들면 다음과 같습니다.

    try {
        int x = a + b * c * d;
        other stuff;
    }
    catch (something) {
        ....
    }
    int y = a + b * c * d;
    use y somehow;

을 지정하지 않으면 try에 할당하도록 계산 된 값을 x"공통 하위 표현식"으로 저장하고에 다시 할당 할 수 y있습니다. 그러나 try첫 번째 표현이 평가되었다는 보장이 없기 때문에 표현을 다시 계산해야합니다. 이것은 일반적으로 "직선"코드에서 큰 문제는 아니지만 루프에서 중요 할 수 있습니다.

그러나 이는 JITC 코드에만 적용됩니다. javac는 적은 양의 최적화 만 수행하며, 바이트 코드 인터프리터는 try블록 을 입력 / 탈출하는 비용이 없습니다 . (블록 경계를 표시하기 위해 생성 된 바이트 코드가 없습니다.)

그리고 최선을 위해 :

public class TryFinally {
    public static void main(String[] argv) throws Throwable {
        try {
            throw new Throwable();
        }
        finally {
            System.out.println("Finally!");
        }
    }
}

산출:

C:\JavaTools>java TryFinally
Finally!
Exception in thread "main" java.lang.Throwable
        at TryFinally.main(TryFinally.java:4)

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
}

"GOTO"가 없습니다.


블록 경계를 표시하기 위해 생성 된 바이트 코드는 없습니다. 이것은 반드시 그런 것은 아닙니다. GOTO에서 블록을 떠나야합니다. 그렇지 않으면 catch/finally프레임으로 넘어갑니다 .
bestsss

@bestsss-GOTO가 생성되었지만 (주어진 것이 아님) 그 비용은 아주 작으며 블록 경계에 대한 "마커"와는 거리가 멀습니다 .GOTO는 많은 구성에 대해 생성 될 수 있습니다.
핫 릭

비용을 언급 한 적이 없지만 바이트 코드가 생성되지 않은 것은 잘못된 진술입니다. 그게 다야. 실제로 바이트 코드에는 블록이 없으며 프레임은 블록과 같지 않습니다.
bestsss

시도가 마지막에 직접 들어가면 GOTO가 없으며 GOTO가없는 다른 시나리오가 있습니다. 요점은 "enter try"/ "exit try"바이트 코드의 순서는 아무것도 없다는 것입니다.
핫 릭

시도가 마침내 False에 빠지면 GOTO가 없습니다 ! finally바이트 코드 에는 없습니다 . try/catch(Throwable any){...; throw any;}그리고 그것은 프레임과 catch 문이 있어야하며 (null이 아닌) 반드시 정의되어야하는 Throwable이 있습니다. 왜 주제에 대해 논쟁하려고합니까, 적어도 일부 바이트 코드를 확인할 수 있습니까? impl에 대한 현재 지침. 마지막으로 블록을 복사하고 이동 섹션 (이전 impl)을 피하지만 바이트 코드는 종료점 수에 따라 복사되어야합니다.
bestsss

8

최적화를 수행 할 수없는 이유를 이해하려면 기본 메커니즘을 이해하는 것이 좋습니다. 내가 찾을 수있는 가장 간결한 예제는 C 매크로에서 구현되었습니다 : 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)

컴파일러는 점프가 X, Y 및 Z로 지역화 될 수 있는지 여부를 결정하는 데 어려움을 겪기 때문에 안전하다고 보장 할 수없는 최적화를 건너 뛰지 만 구현 자체는 다소 가볍습니다.


4
try / catch에 대해 찾은 C 매크로는 Java 또는 C # 구현과 동일하지 않으며 0 런타임 명령을 생성합니다.
Patashu

Java 구현이 너무 광범위하여 포함하기에는 예외 구현 방법에 대한 기본 아이디어를 이해하기위한 단순화 된 구현입니다. 0 런타임 명령을 내 보낸다고 오해의 소지가 있습니다. 예를 들어, 간단한 classcastexception은 다음 과 같은 예외를 확장하는 runtimeexception을 확장합니다. grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… ... C의 스위치 케이스는 다음과 같습니다. 케이스가 하나만 사용 된 경우에는 무료이며 여전히 작은 시작 오버 헤드가 있습니다.
technosaurus

1
@Patashu 프리 컴파일 된 비트는 모두 사용 여부에 관계없이 시작시로드해야합니다. 컴파일 타임에 런타임 동안 메모리 부족 예외가 있는지 알 수있는 방법이 없습니다. 런타임 예외라고 불리는 이유입니다. 그렇지 않으면 컴파일러 경고 / 오류가 발생하므로 모든 것을 최적화하지 않습니다. 이를 처리하기위한 모든 코드는 컴파일 된 코드에 포함되며 시작 비용이 있습니다.
technosaurus

2
C에 대해서는 말할 수 없습니다. C #과 Java에서는 코드가 아닌 메타 데이터를 추가하여 try를 구현합니다. try 블록이 입력되면이를 나타내는 아무것도 실행되지 않습니다. 예외가 발생하면 스택이 해제되고 메타 데이터가 해당 예외 유형의 핸들러 (비싼)를 확인합니다.
Patashu

1
그렇습니다. 실제로 Java 인터프리터 및 정적 바이트 코드 컴파일러를 구현하고 후속 JITC (IBM iSeries 용)에서 작업했으며 바이트 코드에서 try 범위의 시작 / 종료를 "표시"하는 것이 없다는 것을 알 수 있습니다. 범위는 별도의 테이블에서 식별됩니다. 인터프리터는 try 범위에 대해 특별한 작업을 수행하지 않습니다 (예외가 발생할 때까지). JITC (또는 정적 바이트 코드 컴파일러)는 앞에서 설명한 최적화를 억제하기 위해 경계를 알고 있어야합니다.
핫 릭

8

또 다른 마이크로 벤치 마크 ( source ).

예외 백분율에 따라 try-catch 및 no-try-catch 코드 버전을 측정하는 테스트를 만들었습니다. 10 % 백분율은 테스트 사례의 10 %가 0 건으로 나눈 것을 의미합니다. 어떤 상황에서는 try-catch 블록에 의해 처리되고 다른 상황에서는 조건부 연산자에 의해 처리됩니다. 내 결과 테이블은 다음과 같습니다.

OS: Windows 8 6.2 x64
JVM: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 23.25-b01
백분율 | 결과 (시도 / if, ns)   
    0 % | 88/90   
    1 % | 89/87    
    10 % | 86/97    
    90 % | 85/83   

어떤 경우에도 큰 차이가 없다고 말합니다.


3

NullPointException을 잡는 것이 꽤 비싸다는 것을 알았습니다. 1.2k 작업의 경우 시간이 200ms와 12ms로 동일한 방식으로 처리했을 때 나 if(object==null)에게 꽤 개선되었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.