Java 성능에서 예외의 영향은 무엇입니까?


496

질문 : Java에서 예외 처리가 실제로 느립니까?

많은 Google 결과뿐만 아니라 기존의 지혜는 예외적 인 로직을 Java의 정상적인 프로그램 흐름에 사용해서는 안된다고 말합니다. 보통 두 가지 이유가 있습니다.

  1. 그것은 실제로 느리다-심지어 정규 코드보다 훨씬 느리다 (주어진 이유는 다양하다),

  1. 사람들은 예외 코드에서 오류 만 처리되기를 기대하기 때문에 지저분합니다.

이 질문은 # 1에 관한 것입니다.

예를 들어, 이 페이지 는 Java 예외 처리를 "매우 느림"으로 설명하고 느려짐을 예외 메시지 문자열 작성과 관련시킵니다. "이 문자열은 예외 오브젝트를 작성하는 데 사용됩니다. 빠르지는 않습니다." Java의 효과적인 예외 처리 기사에 따르면 "이 이유는 예외 처리의 객체 생성 측면에서 발생하므로 예외를 느리게 throw하는 것"이라고합니다. 또 다른 이유는 스택 추적 생성이 느려지는 것입니다.

내 테스트 (32 비트 Linux에서 Java 1.6.0_07, Java HotSpot 10.0 사용)는 예외 처리가 일반 코드보다 느리지 않음을 나타냅니다. 코드를 실행하는 루프에서 메소드를 실행하려고했습니다. 메소드의 끝에서 부울을 사용하여 return 또는 throw 여부를 나타냅니다 . 이 방법으로 실제 처리는 동일합니다. JVM을 예열하고 있다고 생각하면서 다른 순서로 메소드를 실행하고 테스트 시간을 평균화하려고했습니다. 내 모든 테스트에서, 던져지는 더 빠르지 않으면 (최고 3.1 % 더 빠름) 최소한 반환만큼 빠릅니다. 내 테스트가 잘못되었을 가능성에 완전히 개방되어 있지만 코드 샘플, 테스트 비교 또는 작년 또는 2 년 동안 자바에서 예외 처리를 실제로 보여주는 결과를 보지 못했습니다. 느린.

이 길을 안내하는 것은 정상적인 제어 논리의 일부로 예외를 던진 API를 사용해야했습니다. 사용법을 수정하고 싶었지만 지금은 못할 수도 있습니다. 대신 그들의 전향적인 생각으로 그들을 칭찬해야합니까?

JIT (Just-In-Time) 컴파일의 효율적인 Java 예외 처리 백서 에서 예외가 발생하지 않더라도 예외 처리기 만 있으면 JIT 컴파일러가 코드를 올바르게 최적화하지 못하여 속도가 느려질 수 있다고 제안합니다. . 아직이 이론을 테스트하지 않았습니다.


8
나는 당신이 2)에 대해 묻지 않았다는 것을 알고 있지만, 프로그램 흐름에 예외를 사용하는 것이 GOTO를 사용하는 것보다 낫다는 것을 정말로 알아야합니다. 어떤 사람들은 고토를 방어하고 어떤 사람들은 당신이 말하고있는 것을 방어 할 것입니다. 자신을 사용하기로 결정하기에 충분히 영리하다고 생각한 사람의 이름).
Bill K

80
프로그램 흐름에 예외를 사용하는 것이 GOTO를 사용하는 것보다 낫다고 주장하는 Bill은 프로그램 흐름을 위해 조건부 및 루프를 사용하는 것이 GOTO를 사용하는 것보다 낫다는 것을 주장하는 것보다 낫지 않습니다. 붉은 청어입니다. 자신을 설명하십시오. 예외는 다른 언어로 된 프로그램 흐름에 효과적으로 사용될 수 있습니다. 관용적 파이썬 코드는 예를 들어 예외를 정기적으로 사용합니다. 이 방법으로 Java를 사용하지 않고 예외를 사용하는 코드를 유지할 수 있고 유지 관리했으며 본질적으로 문제가 있다고 생각하지 않습니다.
mmalone

14
패러다임의 선택이 그렇게 되었기 때문에 정상적인 제어 흐름에 예외를 사용하는 @mmalone은 Java에서 나쁜 생각입니다 . Bloch EJ2를 읽으십시오-그는 분명히 인용하면서 (항목 57) exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow-이유에 대한 완전하고 광범위한 설명을 제공합니다. 그리고 그는 Java lib 를 작성한 사람이었습니다 . 따라서 그는 클래스의 API 계약을 정의하는 사람입니다. / 이에 Bill K를 동의하십시오.

8
@ OndraŽižka 만약 어떤 프레임 워크가 이것을 수행한다면 (예외가 아닌 조건에서 예외를 사용하는 경우), 그것은 언어의 예외 클래스 계약을 위반하여 설계 상 결함과 결함이 있습니다. 어떤 사람들은 큰 소리로 코드를 작성한다고해서 덜 시끄럽지 않습니다.

8
stackoverflow.com의 작성자 이외의 예외는 예외입니다. 소프트웨어 개발의 황금률은 결코 단순하고 복잡하지 않습니다. 그는 다음과 같이 썼다. "좋은 오류 검사를했을 때 간단한 3 줄 프로그램이 48 줄로 생겨난다는 것이 사실이지만, 그것은 인생입니다."
sf_jeff

답변:


345

예외가 어떻게 구현되는지에 달려 있습니다. 가장 간단한 방법은 setjmp 및 longjmp를 사용하는 것입니다. 즉, CPU의 모든 레지스터가 스택에 기록되고 (이미 시간이 걸린다) 다른 데이터를 만들어야 할 수도 있습니다.이 모든 것은 이미 try 문에서 발생합니다. throw 문은 스택을 해제하고 모든 레지스터의 값 (및 VM에서 가능한 다른 값)을 복원해야합니다. 따라서 try와 throw는 똑같이 느리고 꽤 느립니다. 그러나 예외가 발생하지 않으면 try 블록을 종료하는 데는 대부분의 경우 시간이 걸리지 않습니다 (메소드가 있으면 스택에 모든 것이 배치되므로 자동으로 정리됩니다).

썬과 다른 사람들은 이것이 차선책 일 수 있으며 물론 VM이 시간이 지남에 따라 더 빨라지고 빨라진다는 것을 인식했습니다. 예외를 구현하는 또 다른 방법이 있습니다. 실제로 빠르게 번개를 시도 할 수 있습니다 (실제로는 전혀 시도하지 않습니다-클래스가 VM에 의해로드 될 때 이미 필요한 모든 작업이 수행됨). . 어떤 새 JVM이이 새롭고 더 나은 기술을 사용하는지 모르겠습니다 ...

...하지만 나중에 코드를 작성하여 특정 시스템의 하나의 JVM에서만 실행되도록 Java로 작성하고 있습니까? 다른 플랫폼이나 다른 JVM 버전 (다른 공급 업체의 경우)에서 실행될 수 있다면 누가 빠른 구현을 사용한다고 누가 말합니까? 빠른 것은 느린 것보다 복잡하고 모든 시스템에서 쉽게 가능하지는 않습니다. 휴대용 상태를 유지하고 싶습니까? 그렇다면 빠른 예외에 의존하지 마십시오.

또한 try 블록 내에서 수행하는 작업에 큰 차이를 만듭니다. try 블록을 열고이 try 블록 내에서 어떤 메소드도 호출하지 않으면 try 블록은 매우 빠릅니다. JIT는 실제로 간단한 던져처럼 던지기를 처리 할 수 ​​있기 때문입니다. 예외가 발생하면 스택 상태를 저장할 필요도없고 스택을 해제 할 필요도 없습니다 (캐치 처리기로만 이동하면 됨). 그러나 이것은 당신이 일반적으로하는 것이 아닙니다. 일반적으로 try 블록을 연 다음 예외가 발생할 수있는 메소드를 호출하십시오. 그리고 메소드 내에서 try 블록을 사용하더라도 다른 메소드를 호출하지 않는 어떤 종류의 메소드입니까? 그냥 숫자를 계산합니까? 그렇다면 예외가 무엇입니까? 프로그램 흐름을 조절하는 훨씬 더 우아한 방법이 있습니다. 단순한 수학 이외의 다른 것에는

다음 테스트 코드를 참조하십시오.

public class Test {
    int value;


    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    // Could in theory throw one, but never will
    public void method2(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            throw new Exception();
        }
    }

    // This one will regularly throw one
    public void method3(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
        // an AND operation between two integers. The size of the number plays
        // no role. AND on 32 BIT always ANDs all 32 bits
        if ((i & 0x1) == 1) {
            throw new Exception();
        }
    }

    public static void main(String[] args) {
        int i;
        long l;
        Test t = new Test();

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            t.method1(i);
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method1 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method2(i);
            } catch (Exception e) {
                System.out.println("You'll never see this!");
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method2 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method3(i);
            } catch (Exception e) {
                // Do nothing here, as we will get here
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method3 took " + l + " ms, result was " + t.getValue()
        );
    }
}

결과:

method1 took 972 ms, result was 2
method2 took 1003 ms, result was 2
method3 took 66716 ms, result was 2

try 블록의 속도 저하가 너무 작아 백그라운드 프로세스와 같은 혼란스러운 요소를 배제 할 수 없습니다. 그러나 catch 블록은 모든 것을 죽였고 66 배 느리게 만들었습니다!

내가 말했듯이 try / catch를 넣고 같은 방법 (method3) 내에서 모두 던지면 결과가 나쁘지 않지만 이것은 내가 의존하지 않는 특별한 JIT 최적화입니다. 그리고이 최적화를 사용할 때도 던지기는 여전히 느립니다. 그래서 나는 당신이 여기서 무엇을하려고하는지 모르겠지만 try / catch / throw를 사용하는 것보다 확실히 더 좋은 방법이 있습니다.


7
좋은 대답이지만 내가 아는 한 System.nanoTime ()은 System.currentTimeMillis ()가 아니라 성능을 측정하는 데 사용해야한다는 것을 덧붙이고 싶습니다.
Simon Forsberg 13:19의

10
@ SimonAndréForsberg nanoTime()에는 Java 1.5가 필요하며 위 코드를 작성하는 데 사용한 시스템에서 Java 1.4 만 사용할 수 있습니다. 또한 실제로 큰 역할을하지 않습니다. 이 둘의 유일한 차이점은 하나는 나노초이고 다른 하나는 밀리 초이며 nanoTime클럭 조작의 영향을받지 않는다는 것 입니다 (사용자 또는 시스템 프로세스가 테스트 코드가 실행되는 순간 시스템 클럭을 정확하게 수정하지 않는 한 관련이 없습니다). 그러나 일반적으로 당신이 옳습니다 nanoTime. 물론 더 나은 선택입니다.
Mecki

2
실제로 테스트는 극단적 인 경우입니다. try블록 이있는 코드에 대해서는 매우 작은 성능 저하를 보여 주지만 no throw. 귀하의 throw테스트는 예외를 던지고 시간의 50 % 가 통과 try. 그것은 실패가 예외적 이지 않은 상황 입니다. 10 %로 줄이면 성능 저하가 크게 줄어 듭니다. 이런 종류의 테스트의 문제점은 사람들이 예외 사용을 완전히 중단하도록 권장한다는 것입니다. 예외적 인 경우 처리를 위해 예외를 사용하면 테스트 결과보다 훨씬 우수한 성능을 발휘합니다.
Nate

1
@Nate 우선, 나는 이것이 예외가 어떻게 구현되는지에 달려 있다고 매우 분명히 말했다. 나는 단지 하나의 특정 구현을 테스트하고 있었지만 많은 것이 있으며 오라클은 모든 릴리스마다 완전히 다른 것을 선택할 수 있습니다. 둘째, 예외가 예외적 인 경우, 일반적으로 어떤 영향을 미치는지, 그 영향은 더 작습니다. 이것은 분명합니다. 명백히 지적해야한다고 생각하지 않으므로 여기에서 요점을 전혀 이해할 수 없습니다. 셋째, 예외 남용은 나쁘고, 모두가 그것에 동의하므로 많은주의를 기울여 사용하는 것이 매우 좋습니다.
Mecki

4
@ 글라이드 던져는 깨끗하지 않습니다 return. 그것은 몸의 중간 어딘가에, 아마도 작업의 중간에 (지금까지 50 % 만 완료 된) 어딘가에 남겨두고 catch블록은 20 스택 프레임 위로 올릴 수 있습니다 (메소드는 trymethod1 mehtod3, ...를 호출하는 method2를 호출하고, 작업 중간에 method20에서 예외가 발생합니다. 스택은 20 프레임 이상 풀어야하고, 완료되지 않은 모든 작업은 취소해야하고 (작업은 절반으로 완료해서는 안 됨) CPU 레지스터는 클린 상태 여야합니다. 이 모든 시간을 소비합니다.
Mecki

255

참고로, 나는 Mecki가 한 실험을 확장했습니다.

method1 took 1733 ms, result was 2
method2 took 1248 ms, result was 2
method3 took 83997 ms, result was 2
method4 took 1692 ms, result was 2
method5 took 60946 ms, result was 2
method6 took 25746 ms, result was 2

첫 번째 3은 Mecki와 동일합니다 (노트북은 분명히 느립니다).

method4는하기 new Integer(1)보다는 오히려 작성한다는 점을 제외하고 method3과 동일합니다 throw new Exception().

method5는 new Exception()throw하지 않고 작성한다는 점을 제외하고 method3과 같습니다 .

method6은 새 예외를 작성하는 대신 미리 작성된 예외 (인스턴스 변수)를 처리한다는 점을 제외하고는 method3과 같습니다.

Java에서 예외를 발생시키는 데 드는 많은 비용은 스택 추적을 수집하는 데 소요되는 시간이며, 이는 예외 오브젝트가 작성 될 때 발생합니다. 예외를 던지는 실제 비용은 크지 만 예외를 만드는 비용보다 훨씬 저렴합니다.


48
+1 귀하의 답변은 핵심 문제를 해결합니다. 스택을 풀고 추적하는 데 걸리는 시간과 2 차 오류 발생. 나는 이것을 최종 답변으로 선택했을 것입니다.
엔지니어

8
좋은. ~ 70 %가 예외를 생성하고 ~ 30 %가 예외를 발생시킵니다. 좋은 정보.
chaqke

1
@Basil-위의 숫자에서 알아낼 수 있어야합니다.
핫 릭

1
구현에 따라 다를 수 있습니다. 이 벤치 마크에 어떤 버전의 Java가 사용 되었습니까?
Thorbjørn Ravn Andersen 님이

3
우리는 표준 코드에서 예외를 생성하고 던지는 경우가 드물게 발생합니다 (런타임이 의미하는 경우). 그렇지 않은 경우 런타임 조건이 매우 나쁘거나 디자인 자체가 문제입니다. 두 경우 모두 공연은 문제가되지 않습니다 ...
Jean-Baptiste Yunès

70

Aleksey Shipilëv는 다양한 조건 조합에서 Java 예외를 벤치마킹 하는 매우 철저한 분석을 수행 했습니다.

  • 새로 작성된 예외와 사전 작성된 예외
  • 스택 추적 활성화 및 비활성화
  • 요청 된 스택 추적 및 요청되지 않은 스택 추적
  • 최상위 레벨에서 잡히고 모든 레벨에서 다시 던지기 vs 모든 레벨에서 체인 / 랩핑
  • 다양한 수준의 Java 호출 스택 깊이
  • 인라인 최적화 없음 vs 극단적 인라인 vs 기본 설정
  • 읽은 사용자 정의 필드와 읽지 않은 필드

또한 다양한 수준의 오류 빈도에서 오류 코드를 확인하는 성능과 비교합니다.

결론 (그의 게시물에서 그대로 인용)은 다음과 같습니다.

  1. 정말 예외적 인 예외는 아름답습니다. 설계대로 사용하고 일반 코드로 처리되는 압도적으로 많은 수의 비 예외 사례 중 실제로 예외적 인 사례 만 전달하는 경우 예외를 사용하면 성능이 향상됩니다.

  2. 예외의 성능 비용에는 두 가지 주요 구성 요소가 있습니다. 예외가 인스턴스화 될 때 스택 추적 구성 과 예외 발생시 스택 해제가 있습니다.

  3. 스택 추적 시공 비용은 예외 인스턴스화 시점의 스택 깊이비례합니다 . 지구상의 누가이 던지기 방법이 호출 될 스택 깊이를 알고 있기 때문에 그것은 이미 나쁜 것입니까? 스택 추적 생성을 끄거나 예외를 캐시하더라도 성능 비용의이 부분 만 제거 할 수 있습니다.

  4. 스택 해제 비용은 컴파일 된 코드에서 예외 처리기를 더 가깝게 만드는 데 얼마나 운이 좋은지에 달려 있습니다. 깊은 예외 처리기 조회를 피하도록 코드를주의해서 구성하면 운이 좋을 수 있습니다.

  5. 두 가지 효과를 모두 제거하면 예외의 성능 비용은 로컬 지점의 비용입니다. 아무리 아름답게 들리더라도 예외를 일반적인 제어 흐름으로 사용해야한다는 의미는 아닙니다.이 경우 컴파일러 최적화의 자비에 달려 있기 때문입니다 ! 당신은 예외 주파수는 정말 예외적 인 경우에 그들을 사용한다 상각 실제 예외를 발생의 가능성 불운 비용을.

  6. 낙관적 인 엄지 손가락 규칙 은 예외에 대한 10 ^ -4 빈도 인 것처럼 보입니다 . 물론 예외 자체의 헤비급, 예외 처리기에서 취한 정확한 조치 등에 달려 있습니다.

예외는 예외가 발생하지 않으면 비용을 지불하지 않기 때문에 예외적 인 조건이 충분히 드문 경우 예외 처리가 if매번 사용하는 것보다 빠릅니다 . 전체 게시물은 읽을 가치가 있습니다.


41

불행히도 내 대답은 너무 길어서 여기에 게시 할 수 없습니다. 여기에 요약하고 http://www.fuwjax.com/how-slow-are-java-exceptions/를 참조하겠습니다 . 를 하십시오.

여기서 진정한 질문은 "실패하지 않는 코드와 비교할 때 '실패로 예외로보고되는 속도가 얼마나 느린가'"가 아닙니다. 받아 들여진 응답으로 당신을 믿을 수 있습니다. 대신 "다른 방법으로보고 된 실패와 비교하여 '실패로 예외로보고되는 속도가 얼마나 느리게 발생합니까?" 일반적으로 실패를보고하는 다른 두 가지 방법은 센티넬 값 또는 결과 래퍼를 사용하는 것입니다.

센티넬 값은 성공한 경우 한 클래스를, 실패한 경우 다른 클래스를 반환하려는 시도입니다. 예외를 던지는 대신 거의 예외를 반환하는 것으로 생각할 수 있습니다. 여기에는 성공 객체와 공유 부모 클래스가 필요하며 "instanceof"점검을 수행하고 성공 또는 실패 정보를 얻기 위해 몇 가지 캐스트를 수행해야합니다.

유형 안전의 위험에 따라 Sentinel 값은 예외보다 빠르지 만 약 2 배 정도입니다. 이제는 많은 것처럼 보일지 모르지만 2 배는 구현 차이의 비용 만 포함합니다. 실제로, 실패 할 수있는 메소드가이 페이지의 다른 샘플 코드에서와 같이 일부 산술 연산자보다 훨씬 흥미 롭기 때문에 요소가 훨씬 더 낮습니다.

반면 결과 랩퍼는 유형 안전성을 전혀 희생하지 않습니다. 성공 및 실패 정보를 단일 클래스로 랩핑합니다. 따라서 "instanceof"대신 "isSuccess ()"및 성공 및 실패 객체 모두에 대한 getter를 제공합니다. 그러나 결과 개체는 예외를 사용하는 것보다 약 2 배 느립니다 . 매번 새로운 래퍼 객체를 만드는 것이 때때로 예외를 던지는 것보다 훨씬 비싸다는 것이 밝혀졌습니다.

또한 메소드가 실패 할 수 있음을 나타내는 방식으로 제공되는 언어는 예외입니다. API를 통해 항상 (대부분) 작동 할 것으로 예상되는 메소드와 실패를보고 할 메소드를 알 수있는 다른 방법은 없습니다.

예외는 센티넬보다 안전하고, 결과 객체보다 빠르며, 어느 것보다 놀랍지 않습니다. try / catch가 if / else를 대체한다고 제안하지는 않지만 비즈니스 로직에서도 예외를보고하는 것이 올바른 방법입니다.

즉, 필자가 실행 한 성능에 실질적으로 영향을 미치는 가장 빈번한 두 가지 방법은 불필요한 객체와 중첩 루프를 만드는 것입니다. 예외를 만들거나 예외를 만들지 않는 것을 선택할 수 있으면 예외를 만들지 마십시오. 예외를 생성하거나 항상 다른 개체를 생성하는 중에서 선택할 수있는 경우 예외를 생성하십시오.


5
보고없이 실패를 확인하는 제어 구현과 비교하여 세 가지 구현의 장기 성능을 테스트하기로 결정했습니다. 프로세스의 실패율은 약 4 %입니다. 테스트 반복은 전략 중 하나에 대해 프로세스를 10000 회 호출합니다. 각 전략은 1000 번 테스트되었으며 마지막 900 번은 통계를 생성하는 데 사용됩니다. 여기에 나노의 평균 시간이 있습니다 : 제어 338 예외 429 결과 348 Sentinel 345
Fuwjax

2
재미를 위해 예외 테스트에서 fillInStackTrace를 비활성화했습니다. 현재 시간은 다음과 같습니다. 제어 347 예외 351 결과 364 센티넬 355
Fuwjax

Fuwjax, 내가 뭔가를 놓치지 않는 한 (그리고 블로그 게시물이 아닌 SO 게시물 만 읽은 것을 인정합니다) 위의 두 의견이 게시물과 모순되는 것처럼 보입니다. 벤치 마크에서 수치가 낮을수록 좋습니다. 이 경우 fillInStackTrace를 사용하여 예외를 생성하면 (기본적이고 일반적인 동작 임) 설명하는 다른 두 기술보다 성능이 느려집니다. 내가 빠진 것이 있습니까? 아니면 실제로 게시물을 반증하기 위해 댓글을 달았습니까?
Felix GV

@Fuwjax-여기에 제시된 "바위와 힘든 곳"선택을 피하는 방법 은 "성공"을 나타내는 객체 를 미리 할당 하는 것입니다. 일반적으로 일반적인 실패 사례에 대해 객체를 미리 할당 할 수도 있습니다. 그런 다음 추가 세부 사항을 다시 전달하는 드문 경우에만 새 오브젝트가 작성됩니다. (이 정수 "오류 코드"의 OO 동등한 플러스 마지막 오류의 세부 사항 얻기 위해 별도의 전화입니다 -. 수십 년 동안 존재했던 기술을)
ToolmakerSteve

@Fuwjax 따라서 예외를 던지더라도 계정으로 개체를 만들지 않습니까? 그 추론을 이해하지 못합니다. 예외를 던지 든 결과 개체를 반환하든 개체를 만드는 중입니다. 그런 의미에서 결과 객체는 예외를 던지는 것보다 느리지 않습니다.
Matthias

20

Java에 대한 스택 추적채우지 않고 @Mecki@incarnate 의 답변을 확장했습니다 .

Java 7 이상에서는을 사용할 수 있습니다 Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace). 그러나 Java6의 경우이 질문에 대한 내 대답을 참조하십시오.

// This one will regularly throw one
public void method4(int i) throws NoStackTraceThrowable {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceThrowable();
    }
}

// This one will regularly throw one
public void method5(int i) throws NoStackTraceRuntimeException {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceRuntimeException();
    }
}

public static void main(String[] args) {
    int i;
    long l;
    Test t = new Test();

    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method4(i);
        } catch (NoStackTraceThrowable e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method4 took " + l + " ms, result was " + t.getValue() );


    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method5(i);
        } catch (RuntimeException e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method5 took " + l + " ms, result was " + t.getValue() );
}

Core i7, 8GB RAM에서 Java 1.6.0_45로 출력 :

method1 took 883 ms, result was 2
method2 took 882 ms, result was 2
method3 took 32270 ms, result was 2 // throws Exception
method4 took 8114 ms, result was 2 // throws NoStackTraceThrowable
method5 took 8086 ms, result was 2 // throws NoStackTraceRuntimeException

따라서 값을 반환하는 메서드는 예외를 throw하는 메서드보다 빠릅니다. IMHO는 성공 및 오류 흐름 모두에 대해 반환 유형을 사용하여 명확한 API를 설계 할 수 없습니다. 스택 추적없이 예외를 발생시키는 메소드는 일반 예외보다 4-5 배 빠릅니다.

편집 : NoStackTraceThrowable.java 감사합니다 @Greg

public class NoStackTraceThrowable extends Throwable { 
    public NoStackTraceThrowable() { 
        super("my special throwable", null, false, false);
    }
}

흥미 롭군 누락 된 클래스 선언은 다음과 같습니다.public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }
Greg Greg

시작할 때 작성 With Java 7+, we can use했지만 나중에 작성 Output with Java 1.6.0_45,했으므로 이것이 Java 6 또는 7 결과입니까?
WBAR

1
Java 7의 @WBAR Throwableboolean writableStackTracearg 가 있는 생성자 를 사용해야합니다 . 그러나 Java 6 이하에는 없습니다. 그렇기 때문에 Java 6 이하에 대한 사용자 정의 구현을 제공했습니다. 위의 코드는 Java 6 이하입니다. 두 번째 파라의 첫 번째 줄을주의 깊게 읽으십시오.
manikanta

@manikanta "IMHO, 우리는 성공 및 오류 흐름 모두에 대해 리턴 유형을 사용하여 명확한 API를 설계 할 수 없습니다."-많은 언어 에서처럼 옵션 / 결과 / 아마도 사용할 수 있습니다.
Hejazzman

@Hejazzman 동의합니다. 그러나 Optional이와 비슷한 것이 Java에 약간 늦었습니다. 그 전에는 성공 / 오류 플래그가있는 래퍼 객체를 사용했습니다. 그러나 그것은 약간의 해킹 인 것처럼 보이고 자연스럽지 않습니다.
마니 칸타

8

얼마 전 나는 두 가지 접근 방식을 사용하여 문자열을 정수로 변환하는 상대적인 성능을 테스트하는 클래스를 작성했습니다. (1) Integer.parseInt ()를 호출하고 예외를 잡거나 (2) 문자열을 정규식과 일치시키고 parseInt () 경기가 성공한 경우에만. 루프를 인터벌하기 전에 Pattern 및 Matcher 객체를 만드는 가장 효율적인 방법으로 정규식을 사용했으며 예외에서 스택 추적을 인쇄하거나 저장하지 않았습니다.

10,000 개의 문자열 목록에서 모두 유효한 숫자 인 경우 parseInt () 접근 방식은 정규식 접근 방식보다 4 배 빠릅니다. 그러나 문자열의 80 % 만 유효하면 정규식은 parseInt ()보다 두 배 빠릅니다. 그리고 20 %가 유효하면 예외가 throw되어 80 %의 시간이 걸리는 것을 의미합니다. 정규 표현식은 parseInt ()보다 약 20 배 빠릅니다.

정규 표현식 접근 방식이 유효한 문자열을 두 번 처리한다는 것을 고려하면 결과에 놀랐습니다. 한 번 일치하고 다시 parseInt (). 그러나 예외를 던지고 잡는 것은 그 이상으로 이루어집니다. 이러한 상황은 실제 상황에서 자주 발생하지는 않지만 예외가 발생하면 예외 포착 기술을 사용해서는 안됩니다. 그러나 사용자 입력 또는 이와 유사한 것만 확인하는 경우 반드시 parseInt () 접근 방식을 사용하십시오.


어떤 JVM을 사용 했습니까? sun-jdk 6에서 여전히 느린가요?
베네딕트 발 보겔

나는 그 답을 제출하기 전에 그것을 파고 JDK 1.6u10에서 다시 실행했으며 그 결과는 내가 게시 한 결과입니다.
Alan Moore

이것은 매우 유용합니다! 감사. 일반적인 사용 사례의 경우 사용자 입력을 구문 분석해야하며 (와 같은 것을 사용하여 Integer.ParseInt()) 사용자 입력이 대부분의 시간 이 맞을 것으로 예상 하므로 사용 사례의 경우 가끔 예외가 발생하는 것처럼 보입니다. .
markvgti 2016 년

8

첫 번째 기사는 호출 스택을 순회하고 스택 추적을 비싼 부분으로 만드는 행위를 언급한다고 생각하지만 두 번째 기사에서는 그렇지 않지만 객체 생성에서 가장 비싼 부분이라고 생각합니다. John Rose는 예외 속도를 높이기위한 다양한 기술을 설명하는 기사를 보유 하고 있습니다. (예외, 스택 추적이없는 예외 등의 사전 배치 및 재사용)

그러나 여전히-나는 이것이 단지 필요한 악, 최후의 수단으로 간주되어야한다고 생각합니다. 이 작업을 수행 한 John의 이유는 JVM에서 아직 사용할 수없는 다른 언어로 기능을 에뮬레이트하기위한 것입니다. 제어 흐름에 예외를 사용하는 습관을 들이지 않아야합니다. 특히 성능상의 이유로 아닙니다! # 2에서 언급 했듯이이 방법으로 코드에서 심각한 버그를 마스킹 할 위험이 있으며 새로운 프로그래머에게는 유지하기가 더 어려울 것입니다.

Java의 Microbenchmarks는 특히 JIT 영역에 들어갈 때 놀랍게도 제대로 이해하기가 어렵습니다. 예를 들어, 테스트에서 2 ~ 5 스택 프레임이 있다고 생각하십니까? 이제 JBoss에 의해 배포 된 JSF 구성 요소에 의해 코드가 호출 될 것입니다. 이제 몇 페이지 길이의 스택 추적이있을 수 있습니다.

테스트 코드를 게시 할 수 있습니까?


7

이 주제가 관련되어 있는지는 모르지만 한 번은 현재 스레드의 스택 추적에 의존하는 트릭을 구현하고 싶었습니다. 인스턴스화 된 클래스 내에서 인스턴스화를 유발 한 메소드의 이름을 발견하고 싶었습니다. 나는 그것을 완전히 포기했다). 그래서 호출 Thread.currentThread().getStackTrace()매우 느리다 는 것을 발견했습니다 ( dumpThreads내부에서 사용하는 기본 메소드 로 인해 ).

따라서 이에 Throwable상응하는 Java 에는 기본 메소드가 fillInStackTrace있습니다. catch앞서 설명한 킬러 블록이 어떻게 든이 메소드의 실행을 유발 한다고 생각합니다 .

하지만 또 다른 이야기를 들려 드리겠습니다 ...

스칼라에서는 일부 기능 기능이를 사용하여 JVM에서 컴파일 ControlThrowable되는데,이 기능은 다음과 같은 방식으로 확장 Throwable및 재정의됩니다 fillInStackTrace.

override def fillInStackTrace(): Throwable = this

그래서 위의 테스트를 수정했습니다 (사이클 양이 10 줄어 들었습니다. 제 기계가 약간 느립니다 :).

class ControlException extends ControlThrowable

class T {
  var value = 0

  def reset = {
    value = 0
  }

  def method1(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      println("You'll never see this!")
    }
  }

  def method2(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      throw new Exception()
    }
  }

  def method3(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new Exception()
    }
  }

  def method4(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new ControlException()
    }
  }
}

class Main {
  var l = System.currentTimeMillis
  val t = new T
  for (i <- 1 to 10000000)
    t.method1(i)
  l = System.currentTimeMillis - l
  println("method1 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method2(i)
  } catch {
    case _ => println("You'll never see this")
  }
  l = System.currentTimeMillis - l
  println("method2 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method4(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method4 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method3(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method3 took " + l + " ms, result was " + t.value)

}

결과는 다음과 같습니다.

method1 took 146 ms, result was 2
method2 took 159 ms, result was 2
method4 took 1551 ms, result was 2
method3 took 42492 ms, result was 2

당신 사이의 유일한 차이는, 참조 method3하고 method4그들이 예외의 다른 종류를 던질 것입니다. 엡, method4보다 여전히 느린 method1하고 method2있지만, 그 차이는 훨씬 더 허용됩니다.


6

JVM 1.5에서 일부 성능 테스트를 수행했으며 예외를 사용하는 것이 최소 2 배 느 렸습니다. 평균 : 예외적으로 3 배 (3 배) 이상의 사소한 작은 방법에서 실행 시간. 예외를 잡아야하는 사소한 작은 루프로 인해 자기 시간이 2 배 증가했습니다.

프로덕션 코드와 마이크로 벤치 마크에서 비슷한 숫자를 보았습니다.

예외는 확실히해야 하지 자주라고 아무것도 사용할 수. 1 초에 수천 건의 예외가 발생하면 병목이 커질 수 있습니다.

예를 들어 "Integer.ParseInt (...)"를 사용하여 매우 큰 텍스트 파일에서 모든 잘못된 값을 찾을 수 있습니다. (나는이 유틸리티 방법 본 생산 코드의 성능을)

예외를 사용하여 사용자 GUI 양식에 잘못된 값을보고하면 성능 관점에서는 그리 나쁘지 않습니다.

좋은 디자인 관행에 관계없이 규칙을 따르십시오. 오류가 정상 / 예상이라면 반환 값을 사용하십시오. 비정상적인 경우 예외를 사용하십시오. 예를 들어, 사용자 입력을 읽을 때 잘못된 값은 정상입니다. 오류 코드를 사용하십시오. 내부 유틸리티 함수에 값을 전달하면 코드를 호출하여 잘못된 값을 필터링해야합니다. 예외를 사용하십시오.


유용한 몇 가지 제안을 해보자. Integer.valueOf (String) 대신 형식의 숫자가 필요한 경우 정규 표현식 매처를 대신 사용해야합니다. 패턴을 사전 컴파일하고 재사용하여 매처를 저렴하게 만들 수 있습니다. 그러나 GUI 양식에서 isValid / validate / checkField 또는 무엇을 가지고 있는지 더 명확합니다. 또한 Java 8에는 선택적 모나드가 있으므로 사용을 고려하십시오. (답은 9 살이지만 여전히! : p)
Haakon Løtveit

4

Java 및 C #의 예외 성능은 바람직하지 않습니다.

프로그래머로서 이것은 실제적인 성능상의 이유로 "예외가 드물게 발생해야한다"는 규칙에 따라 우리를 살도록 강요합니다.

그러나 컴퓨터 과학자로서 우리는이 문제가있는 상태에 반항해야합니다. 함수를 작성하는 사람은 종종 얼마나 자주 호출되는지, 또는 성공 또는 실패 가능성에 대해 전혀 모릅니다. 발신자에게만이 정보가 있습니다. 예외를 피하려고하면 명확하지만 느리게 예외 버전 만 있고 다른 경우에는 빠르지 만 번거로운 반환 값 오류가있는 경우 API idom이 명확하지 않게되며 다른 경우에는 . 라이브러리 구현자는 두 가지 버전의 API를 작성하고 유지 보수해야 할 수 있으며 호출자는 각 상황에서 사용할 두 가지 버전 중 하나를 결정해야합니다.

이것은 일종의 혼란입니다. 예외가 더 나은 성능을 보인 경우, 이러한 어리석은 관용구를 피하고 예외를 구조적 오류 반환 기능으로 사용하도록 의도 된대로 사용할 수 있습니다.

실제로 반환 값에 더 가까운 기술을 사용하여 구현 된 예외 메커니즘을보고 싶습니다. 따라서 반환 값에 더 가까운 성능을 가질 수 있습니다. 이것이 성능에 민감한 코드로 되돌아 가기 때문입니다.

다음은 예외 성능과 오류 반환 값 성능을 비교하는 코드 샘플입니다.

공공 수업 TestIt {

int value;


public int getValue() {
    return value;
}

public void reset() {
    value = 0;
}

public boolean baseline_null(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        return shouldfail;
    } else {
        return baseline_null(shouldfail,recurse_depth-1);
    }
}

public boolean retval_error(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            return false;
        } else {
            return true;
        }
    } else {
        boolean nested_error = retval_error(shouldfail,recurse_depth-1);
        if (nested_error) {
            return true;
        } else {
            return false;
        }
    }
}

public void exception_error(boolean shouldfail, int recurse_depth) throws Exception {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            throw new Exception();
        }
    } else {
        exception_error(shouldfail,recurse_depth-1);
    }

}

public static void main(String[] args) {
    int i;
    long l;
    TestIt t = new TestIt();
    int failures;

    int ITERATION_COUNT = 100000000;


    // (0) baseline null workload
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                t.baseline_null(shoulderror,recurse_depth);
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("baseline: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }


    // (1) retval_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                if (!t.retval_error(shoulderror,recurse_depth)) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("retval_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }

    // (2) exception_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                try {
                    t.exception_error(shoulderror,recurse_depth);
                } catch (Exception e) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("exception_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);              
        }
    }
}

}

결과는 다음과 같습니다.

baseline: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 683 ms
baseline: recurse_depth 2, exception_freqeuncy 0.25 (0), time elapsed 790 ms
baseline: recurse_depth 2, exception_freqeuncy 0.5 (0), time elapsed 768 ms
baseline: recurse_depth 2, exception_freqeuncy 0.75 (0), time elapsed 749 ms
baseline: recurse_depth 2, exception_freqeuncy 1.0 (0), time elapsed 731 ms
baseline: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 923 ms
baseline: recurse_depth 5, exception_freqeuncy 0.25 (0), time elapsed 971 ms
baseline: recurse_depth 5, exception_freqeuncy 0.5 (0), time elapsed 982 ms
baseline: recurse_depth 5, exception_freqeuncy 0.75 (0), time elapsed 947 ms
baseline: recurse_depth 5, exception_freqeuncy 1.0 (0), time elapsed 937 ms
baseline: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1154 ms
baseline: recurse_depth 8, exception_freqeuncy 0.25 (0), time elapsed 1149 ms
baseline: recurse_depth 8, exception_freqeuncy 0.5 (0), time elapsed 1133 ms
baseline: recurse_depth 8, exception_freqeuncy 0.75 (0), time elapsed 1117 ms
baseline: recurse_depth 8, exception_freqeuncy 1.0 (0), time elapsed 1116 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 742 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 743 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 734 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 723 ms
retval_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 728 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 920 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 1121   ms
retval_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 1037 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 1141   ms
retval_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 1130 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1218 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 1334  ms
retval_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 1478 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 1637 ms
retval_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 1655 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 726 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 17487   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 33763   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 67367   ms
exception_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 66990 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 924 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 23775  ms
exception_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 46326 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 91707 ms
exception_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 91580 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1144 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 30440 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 59116   ms
exception_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 116678 ms
exception_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 116477 ms

반환 값을 확인하고 전파하면 기준-널 콜에 약간의 비용이 추가되고 해당 비용은 콜 깊이에 비례합니다. 콜 체인 깊이가 8 인 경우 오류 반환 값 확인 버전은 반환 값을 확인하지 않은 기본 버전보다 약 27 % 느 렸습니다.

예외 성능은 콜 깊이의 기능이 아니라 예외 빈도의 기능입니다. 그러나 예외 빈도가 증가함에 따라 탈지가 훨씬 더 극적입니다. 25 %의 오류 빈도로 코드가 24-TIMES 느리게 실행되었습니다. 100 %의 오류 빈도에서 예외 버전은 거의 100 배 느립니다.

이것은 아마도 우리의 예외 구현에서 잘못된 트레이드 오프를 만들고 있음을 나타냅니다. 값 비싼 스토킹 워크를 피하거나 컴파일러에서 지원하는 반환 값 확인으로 완전히 전환하면 예외가 더 빨라질 수 있습니다. 그들이 할 때까지, 우리는 코드가 빨리 실행되기를 원할 때 피해야합니다.


3

HotSpot은 인라인 된 시스템 생성 예외에 대한 예외 코드를 제거 할 수 있습니다. 그러나 명시 적으로 생성 된 예외와 제거되지 않은 예외는 스택 추적을 만드는 데 많은 시간이 걸립니다. fillInStackTrace이것이 성능에 미치는 영향을 확인 하려면 재정의 하십시오.


2

예외를 던지는 것이 느리지 않더라도 정상적인 프로그램 흐름에 대해 예외를 던지는 것은 여전히 ​​나쁜 생각입니다. 이 방법으로 GOTO와 유사합니다 ...

그래도 그 질문에 실제로 대답하지는 않습니다. 예외를 느리게 던지는 '전통적인'지혜는 이전 자바 버전 (<1.4)에서 사실이라고 생각합니다. 예외를 만들려면 VM에서 전체 스택 추적을 만들어야합니다. 그 이후로 VM에서 많은 것들이 속도를 높이기 위해 변경되었으며 이것은 개선 된 영역 중 하나 일 것입니다.


1
"정상적인 프로그램 흐름"을 정의하는 것이 좋습니다. 확인 된 예외를 비즈니스 프로세스 실패로 사용하고 복구 할 수없는 실패에 대해 확인되지 않은 예외를 사용하는 것에 대해 많은 글을 썼기 때문에 비즈니스 로직의 실패는 여전히 정상적인 흐름으로 생각할 수 있습니다.
스펜서 Kormos

2
@Spencer K : 이름에서 알 수 있듯이 예외는 예외적 인 상황이 발견되었음을 의미합니다 (파일이 사라지고 네트워크가 갑자기 닫혔습니다 ...). 이는 상황이 예외가 아님을 의미합니다. 상황이 발생할 것으로 예상되면 예외를 사용하지 않을 것입니다.
Mecki

2
@Mecki : 그렇습니다. 나는 최근 이것에 대해 누군가와 토론을했습니다 ... 그들은 유효성 검사 프레임 워크를 작성 중이며 유효성 검사 실패의 경우 예외를 던졌습니다. 나는 이것이 일반적 일 것이기 때문에 이것이 나쁜 생각이라고 생각합니다. 차라리 메소드가 ValidationResult를 반환하는 것을 볼 수 있습니다.
user38051

2
제어 흐름의 관점에서, 예외는 유사 break하거나 return하는 없다 goto.
핫 릭

3
수많은 프로그래밍 패러다임이 있습니다. 그 의미가 무엇이든 단일 "정상 흐름"은있을 수 없습니다. 기본적으로 예외 메커니즘은 현재 프레임을 빠르게 떠나 특정 지점까지 스택을 해제하는 방법입니다. "예외"라는 단어는 "예기치 않은"본성에 대해 아무 것도 암시하지 않습니다. 간단한 예 : 라우팅 환경을 따라 특정 상황이 발생할 때 웹 애플리케이션에서 404를 "던지기"는 것은 매우 당연합니다. 왜 그 논리가 예외와 함께 구현되지 않습니까? 안티 패턴은 무엇입니까?
화신

2

Integer.parseInt를 다음 메소드와 비교해 봅시다. 다음 메소드는 예외를 던지는 대신 구문 분석 할 수없는 데이터의 경우 기본값을 리턴합니다.

  public static int parseUnsignedInt(String s, int defaultValue) {
    final int strLength = s.length();
    if (strLength == 0)
      return defaultValue;
    int value = 0;
    for (int i=strLength-1; i>=0; i--) {
      int c = s.charAt(i);
      if (c > 47 && c < 58) {
        c -= 48;
        for (int j=strLength-i; j!=1; j--)
          c *= 10;
        value += c;
      } else {
        return defaultValue;
      }
    }
    return value < 0 ? /* übergebener wert > Integer.MAX_VALUE? */ defaultValue : value;
  }

두 가지 방법을 모두 "유효한"데이터에 적용하는 한 Integer.parseInt가 더 복잡한 데이터를 처리 할지라도 두 방법은 거의 같은 속도로 작동합니다. 그러나 잘못된 데이터를 구문 분석하려고하면 (예 : "abc"를 1.000.000 회 구문 분석) 성능의 차이가 필수적입니다.


2

예외 성능에 대한 훌륭한 게시물은 다음과 같습니다.

https://shipilev.net/blog/2014/exceptional-performance/

스택 추적을 사용하거나 사용하지 않고 기존 인스턴스의 인스턴스화 및 재사용 :

Benchmark                            Mode   Samples         Mean   Mean error  Units

dynamicException                     avgt        25     1901.196       14.572  ns/op
dynamicException_NoStack             avgt        25       67.029        0.212  ns/op
dynamicException_NoStack_UsedData    avgt        25       68.952        0.441  ns/op
dynamicException_NoStack_UsedStack   avgt        25      137.329        1.039  ns/op
dynamicException_UsedData            avgt        25     1900.770        9.359  ns/op
dynamicException_UsedStack           avgt        25    20033.658      118.600  ns/op

plain                                avgt        25        1.259        0.002  ns/op
staticException                      avgt        25        1.510        0.001  ns/op
staticException_NoStack              avgt        25        1.514        0.003  ns/op
staticException_NoStack_UsedData     avgt        25        4.185        0.015  ns/op
staticException_NoStack_UsedStack    avgt        25       19.110        0.051  ns/op
staticException_UsedData             avgt        25        4.159        0.007  ns/op
staticException_UsedStack            avgt        25       25.144        0.186  ns/op

스택 깊이 추적에 따라

Benchmark        Mode   Samples         Mean   Mean error  Units

exception_0000   avgt        25     1959.068       30.783  ns/op
exception_0001   avgt        25     1945.958       12.104  ns/op
exception_0002   avgt        25     2063.575       47.708  ns/op
exception_0004   avgt        25     2211.882       29.417  ns/op
exception_0008   avgt        25     2472.729       57.336  ns/op
exception_0016   avgt        25     2950.847       29.863  ns/op
exception_0032   avgt        25     4416.548       50.340  ns/op
exception_0064   avgt        25     6845.140       40.114  ns/op
exception_0128   avgt        25    11774.758       54.299  ns/op
exception_0256   avgt        25    21617.526      101.379  ns/op
exception_0512   avgt        25    42780.434      144.594  ns/op
exception_1024   avgt        25    82839.358      291.434  ns/op

다른 세부 사항 (JIT의 x64 어셈블러 포함)은 원래 블로그 게시물을 읽으십시오.

즉, Hibernate / Spring / etc-EE-shit는 예외 (xD)로 인해 느리고 앱 제어 흐름을 예외에서 다시 작성합니다 ( continure/를 호출하고 C에서와 같이 플래그를 메소드 호출에서 break반환 boolean)하여 애플리케이션의 성능을 10x-100x 향상시킵니다. 얼마나 자주 던지는 지에 따라))


0

Exception을 아무것도로 대체 할 수 없으므로 method1이 호출 메소드에서 부울과 검사를 반환하도록 위의 @Mecki의 대답을 변경했습니다. 두 번의 실행 후에 method1은 여전히 ​​method2만큼 빠르거나 빠릅니다.

다음은 코드의 스냅 샷입니다.

// Calculates without exception
public boolean method1(int i) {
    value = ((value + i) / i) << 1;
    // Will never be true
    return ((i & 0xFFFFFFF) == 1000000000);

}
....
   for (i = 1; i < 100000000; i++) {
            if (t.method1(i)) {
                System.out.println("Will never be true!");
            }
    }

결과 :

실행 1

method1 took 841 ms, result was 2
method2 took 841 ms, result was 2
method3 took 85058 ms, result was 2

실행 2

method1 took 821 ms, result was 2
method2 took 838 ms, result was 2
method3 took 85929 ms, result was 2

0

예외 처리를위한 것입니다 런타임에 예상치 못한 조건을 만.

컴파일 시간에 수행 할 수있는 간단한 유효성 검사 대신 예외를 사용하면 실행 시간까지 유효성 검사가 지연됩니다. 결과적으로 프로그램의 효율성이 떨어집니다.

간단한 if..else 유효성 검사 를 사용하는 대신 예외를 발생 시키면 코드 작성 및 유지 관리가 복잡해집니다.


-3

예외 속도 대 프로그래밍 방식으로 데이터 확인에 대한 내 의견.

많은 클래스에는 String to Value 변환기 (스캐너 / 파서)가 있으며 존경 받고 잘 알려진 라이브러리도 있습니다.)

보통 형태가 있습니다

class Example {
public static Example Parse(String input) throws AnyRuntimeParsigException
...
}

예외 이름은 단지 예일 뿐이며 일반적으로 선택 해제되어 있습니다 (런타임).

때로는 두 번째 형태가 존재합니다.

public static Example Parse(String input, Example defaultValue)

절대 던지지

두 번째를 사용할 수 없거나 프로그래머가 너무 적은 문서를 읽고 먼저 사용하는 경우 정규식으로 해당 코드를 작성하십시오. 정규 표현은 시원하고 정치적으로 정확합니다.

Xxxxx.regex(".....pattern", src);
if(ImTotallySure)
{
  Example v = Example.Parse(src);
}

이 코드를 사용하면 프로그래머는 예외 비용을 들이지 않습니다. 그러나 정규 표현식의 비용은 항상 높지만 때로는 적은 비용의 예외가 있습니다.

나는 거의 항상 그런 맥락에서 사용합니다

try { parse } catch(ParsingException ) // concrete exception from javadoc
{
}

stacktrace 등을 분석하지 않고 강의를 마친 후 속도가 매우 빠릅니다.

예외를 두려워하지 마십시오


-5

예외가 정상 수익보다 느린 이유는 무엇입니까?

스택 트레이스를 터미널에 인쇄하지 않고 파일이나 비슷한 파일로 저장하면 catch-block은 다른 코드 블록보다 더 많은 작업을 수행하지 않습니다. 따라서 "throw new my_cool_error ()"가 왜 그렇게 느려 야하는지 상상할 수 없습니다.

좋은 질문이며이 주제에 대한 추가 정보를 기대합니다!


17
실제로 사용되지 않더라도 스택 추적에 대한 정보를 캡처해야합니다.
Jon Skeet
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.