예외가 발생하지 않더라도 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.