424

질문 : Java의 예외 처리가 실제로 느린가요?

기존의 지혜와 많은 Google 결과에는 예외적 인 로직을 Java의 정상적인 프로그램 흐름에 사용해서는 안됩니다. 보통 두 가지 이유가있다.

  1. 그것은 정말로 느립니다. 심지어 정규 코드보다 느린 순서 일 수도 있습니다 (주어진 이유는 다양합니다).

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

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

예로서,이 페이지Java 예외 처리를 "매우 느린"것으로 설명하고 예외 메시지 문자열 생성 속도가 느린 것과 관련이 있습니다. "이 문자열은 throw되는 예외 객체를 만드는 데 사용됩니다. 기사Java의 효과적인 예외 처리"이 이유는 예외 처리의 객체 생성 측면에 기인합니다. 따라서 예외를 던지는 것이 본질적으로 느리게 만듭니다"라고 말합니다. 스택 추적 생성이 속도를 늦추는 또 다른 이유가 있습니다.

내 테스트 (32 비트 Linux에서 Java 1.6.0_07, Java HotSpot 10.0 사용)는 예외 처리가 일반 코드보다 느리다는 것을 나타냅니다. 일부 코드를 실행하는 루프에서 메서드를 실행 해 보았습니다. 메소드의 끝에서, 나는 boolean을 사용하여반환또는던지다. 이 방법은 실제 처리가 동일합니다. 다른 순서로 메소드를 실행하고 테스트 시간을 평균화하여 JVM이 워밍업되었을 수 있다고 생각했습니다. 내 모든 테스트에서 던지기는 적어도 빠르지는 않지만 빠름 (최대 3.1 % 빠름)보다 빠른 속도로 돌아 왔습니다. 필자의 테스트가 잘못되었다는 가능성에 완전히 열려 있지만, 코드 샘플, 테스트 비교 또는 지난 1-2 년 동안 실제로 Java에서 예외 처리를 보여주는 결과에 대해서는 아무 것도 보지 못했습니다. 느린.

이 길로 나를 이끌었던 것은 정상적인 제어 논리의 일부로 예외를 던지기 위해 사용해야했던 API였습니다. 나는 그들의 사용법에서 그들을 교정하고 싶었지만, 지금 나는 할 수 없을지도 모른다. 나는 앞으로 그들의 생각에 대해 칭찬해야 할 것인가?

논문에서Just-In-Time 컴파일에서 효율적인 Java 예외 처리저자는 예외가 발생하지 않더라도 예외 처리기 만 있으면 JIT 컴파일러가 코드를 올바르게 최적화하지 못하도록하여 속도를 느리게 할 것을 제안합니다. 나는이 이론을 아직 시험하지 않았다.


  • 2)에 대해 묻지는 않았지만 실제로 프로그램 흐름에 대한 예외 사용은 GOTO를 사용하는 것보다 낫지 않다는 것을 인식해야합니다. 어떤 사람들은 말을 지키고, 어떤 사람들은 당신이 말하는 것을 지킬 것입니다. 그러나 어느 정도 기간 동안 구현하고 유지 보수 한 사람에게 물으면, 둘 다 디자인 습관을 유지하기가 어렵습니다. (그리고 아마도 저주 할 것입니다. 그들이 자신을 사용하기로 결정하기에 충분히 똑똑하다고 생각한 사람의 이름). - Bill K
  • 프로그램 흐름에 예외를 사용하는 것이 GOTO를 사용하는 것보다 낫다고 주장하는 Bill은 프로그램 흐름에 대한 조건문과 루프를 사용하는 것이 GOTO를 사용하는 것보다 낫다고 주장하는 것보다 낫지 않습니다. 그것은 빨간색 청어입니다. 자신을 설명하십시오. 예외는 다른 언어의 프로그램 흐름에 효과적으로 사용될 수 있으며 사용됩니다. 관용적 인 파이썬 코드는 예를 들어 예외를 정기적으로 사용합니다. 이 방법으로 예외를 사용하는 코드 (자바가 아님)를 유지 관리 할 수 있고, 본질적으로 잘못되었다고 생각하지 않습니다. - mmalone
  • @mmalone을 사용하여 정상적인 제어 흐름에 대한 예외는 Java에서 좋지 않은 개념입니다.패러다임 선택은 그런 식으로 이루어졌다.. Bloch EJ2를 읽으십시오 - 그는 분명히, 인용문 (Item 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에 동의합니다. - vaxquis
  • @ OndraŽižka 일부 프레임 워크가 예외적 인 경우 예외를 사용하는 경우 언어의 Exception 클래스 계약을 위반하여 설계 상 결함이 발생하여 고장났습니다. 일부 사람들은 형편없는 코드를 작성하지 않기 때문에 덜 비참하게 만들지 않습니다. - vaxquis
  • btw, 그것은 mmalone의 코멘트가 "나는 그것이 본질적으로 잘못되었다고 생각하지 않는다"고 말했음에도 불구하고 너무 많은 upvotes를 얻었습니다. (비록이 경우에 참조가 분명하지만) 사람들이 그것에 동의하는 것을 볼 수있어서 슬프다. SO "community quality assurance"가 얼마나 심각한지를 강조하기 때문이다. 나에게 시간이지나면서 악화되었다. - vaxquis

17 답변


313

예외가 구현되는 방법에 따라 다릅니다. 가장 간단한 방법은 setjmp와 longjmp를 사용하는 것입니다. 즉, CPU의 모든 레지스터가 스택에 쓰여지고 (이미 시간이 걸립니다), 아마도 다른 데이터를 만들어야 할 필요가 있습니다.이 모든 것은 이미 try 문에서 발생합니다. throw 문은 스택을 풀고 모든 레지스터 값 (및 VM의 가능한 다른 값)을 복원해야합니다. 따라서 try와 throw는 똑같이 느리고 꽤 느리다. 아무런 예외도 던지지 않으면 대부분의 경우 try 블록을 빠져 나오지 않아도된다. (모든 것이 스택 상에 존재하기 때문에 메서드가 있으면 자동으로 정리된다.)

Sun과 다른 사람들은 이것이 차선책 일 가능성이 있으며 물론 VM이 당분간 빠르고 더 빨라진다는 것을 알고 있습니다. 예외를 구현하는 또 다른 방법은 번개를 빠르게 시도하는 것입니다 (일반적으로 시도해도 아무 일도 일어나지 않습니다. 발생해야하는 모든 것은 이미 클래스가 VM에 의해로드 될 때 수행됩니다). . 어떤 JVM이이 새롭고 더 나은 기술을 사용하는지 모르겠습니다 ...

...하지만 자바로 작성하고 나중에 특정 시스템에서 하나의 JVM에서만 코드를 실행할 수 있습니까? 다른 플랫폼이나 다른 JVM 버전 (다른 공급 업체 일 수도 있음)에서 실행될 수 있다면 누가 빠른 구현을 사용한다고 말합니까? 빠른 시스템은 느린 시스템보다 복잡하며 모든 시스템에서 쉽게 불가능합니다. 당신은 휴대용을 유지하고 싶습니까? 그런 다음 빠른 예외에 의존하지 마십시오.

또한 try 블록 내에서하는 일도 큰 차이가 있습니다. try 블록을 열고이 try 블록 내에서 어떤 메소드도 호출하지 않으면 JIT가 실제로 간단한 goto처럼 throw를 처리 할 수 있으므로 try 블록은 매우 빠릅니다. 스택 상태를 저장할 필요도없고 예외가 발생하면 스택을 풀지 않아도됩니다 (catch 핸들러로만 이동하면됩니다). 그러나 이것은 보통하는 일이 아닙니다. 일반적으로 try 블록을 연 다음 예외를 throw 할 수있는 메서드를 호출합니다. 그리고 여러분의 메서드 내에서 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를 넣고 같은 method (method3) 내에서 모두 던지면 그 결과는 나쁘지 않을 것이다. 그러나 이것은 내가 의존하지 않는 특별한 JIT 최적화이다. 그리고이 최적화를 사용할 때조차도 여전히 매우 느립니다. 그래서 당신이 여기서 무엇을하려고하는지 모르겠지만 try / catch / throw를 사용하는 것보다 더 나은 방법이 있습니다.


  • 고마워, 내 예외 테스트를 위해 스택에 깊이 들어가 보지 못했을 것 같아. 이것은 내가 찾고 있었던 바로 그 것이다. - John Ellinwood
  • 훌륭한 답변이지만, 지금까지 내가 아는 한 System.currentTimeMillis ()가 아닌 성능 측정에 System.nanoTime ()을 사용해야한다고 덧붙이고 싶습니다. - Simon Forsberg
  • @ SimonAndr & # 233; 포스 버그nanoTime()Java 1.5가 필요하고 위에 코드를 작성하는 데 사용한 시스템에서 Java 1.4 만 사용할 수있었습니다. 또한 실제로는 큰 역할을하지 않습니다. 이 둘의 유일한 차이점은 하나는 다른 1 밀리 초와 나노초이고nanoTime클럭 조작 (사용자 또는 시스템 프로세스가 테스트 코드가 실행되는 순간 시스템 시계를 수정하지 않는 한 상관 관계가 없음)에 영향을받지 않습니다. 일반적으로 당신은 맞습니다, 그래도,nanoTime물론 더 나은 선택입니다. - Mecki
  • 시험이 극단적 인 경우에주의해야합니다. 코드가있는 경우 성능이 매우 저하됩니다.try차단, 아니요throw. 너의throw테스트가 예외를 던지고있다.시간의 50 %그것은try. 그것은 실패가 아닌 상황을 명확하게 나타냅니다.특별한. 10 % 만 줄이면 실적이 크게 떨어집니다. 이런 종류의 테스트의 문제점은 사람들이 예외 사용을 완전히 중단하도록 권장한다는 것입니다. 뛰어난 예외 처리를 위해 예외를 사용하면 테스트 결과보다 훨씬 뛰어난 성능을 발휘합니다. - Nate
  • @ 글라이드 던지기는 깨끗한 것과 같지 않다.return. 그것은 몸의 한가운데 어딘가에, 어쩌면 수술 중간에 (아마도 지금까지 50 % 만 완료되었습니다) 그리고catch블록은 20 스택 프레임 이상이 될 수 있습니다 (메소드에는try블록, mehtod3, ...을 호출하는 method2를 호출하는 method1을 호출하고, 작업 중간에 method20에서 예외가 발생합니다. 스택은 20 프레임 위쪽으로 풀어야하며, 완료되지 않은 모든 작업을 실행 취소해야합니다 (작업을 절반으로 완료하면 안됩니다). CPU 레지스터는 깨끗한 상태 여야합니다. 이것은 모두 시간을 소비합니다. - Mecki

228

참고로, 나는 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는 method3과 동일하지만new Integer(1)하고있는 것보다throw new Exception().

method5는 method3와 비슷하지만new Exception()던지지 않고.

method6는 method3와 비슷하지만, 새로 작성하는 대신 미리 작성된 예외 (인스턴스 변수)를 던집니다.

자바에서 예외를 던지는 데 드는 많은 비용은 예외 객체가 생성 될 때 발생하는 스택 추적을 수집하는 데 소요되는 시간입니다. 예외를 던지기위한 실제 비용은 크지 만 예외를 생성하는 비용보다 상당히 적습니다.


  • +1 귀하의 답변은 핵심 문제 - 스택을 풀어서 추적하는 데 걸리는 시간과 2 차로 오류를 던지는 데 걸리는 시간 -를 다룹니다. 나는 이것을 최종 답으로 선택했을 것이다. - Arcane Engineer
  • 좋은. ~ 70 % 예외를 생성, ~ 30 % 그것을 던지고. 좋은 정보. - chaqke
  • 비슷한 질문으로, 예외를 잡아서 다시 던지고 다시 잡는 데 얼마나 많은 여분의 오버 헤드가 있습니까? 감사. - Basil
  • @Basil - 당신은 위의 숫자에서 그걸 알아낼 수 있어야합니다. - Hot Licks
  • 우리는 표준 코드에서 예외를 생성하고 던지는 일은 드문 경우 (런타임에 의미하는 경우) 발생합니다. 그렇지 않은 경우 런타임 조건이 매우 좋지 않거나 디자인 자체가 문제 일 수 있습니다. 두 경우 모두 공연은 중요하지 않습니다 ... - Jean-Baptiste Yunès

55

Aleksey Shipilëv는 (는)매우 철저한 분석다양한 조건의 조합에서 Java 예외를 벤치마킹합니다.

  • 새로 생성 된 예외 대 미리 작성된 예외
  • 사용 가능한 스택 추적 기능과 사용 중지 된 기능
  • 요청 된 스택 추적은 결코 요청되지 않았습니다.
  • 모든 레벨에서 다시 뽑아 내고 최상위 레벨에서 잡은 대 모든 레벨에서 체인으로 묶여 감싸였습니다.
  • 다양한 수준의 Java 호출 스택 깊이
  • 극한 인라인 대 기본 설정이없는 인라인 최적화가 없습니다.
  • 사용자 정의 필드 읽기는 읽지 않음

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

결론 (그의 게시물에서 축 어적으로 인용)은 다음과 같습니다 :

  1. 정말로 예외적 인 예외는 아름답게 연기됩니다.설계대로 사용하고 일반 코드로 처리되는 압도적으로 많은 수의 비 예외적 인 경우 중 진정한 예외적 인 경우 만 통신하면 예외를 사용하면 성능이 향상됩니다.

  2. 예외의 성능 비용에는 두 가지 주요 구성 요소가 있습니다.스택 트레이스 구성예외가 인스턴스화 될 때풀 풀다Exception throw 중.

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

  4. 스택 풀기 비용은 예외 처리기를 컴파일 된 코드에 가깝게 가져 오는 것이 얼마나 행운인지에 달려 있습니다.심층적 인 예외 핸들러 검색을 피하기 위해 코드를 조심스럽게 구조화하면 아마 우리가 운 좋게 될 것입니다.

  5. 두 가지 효과를 모두 제거해야하는 경우 예외의 성능 비용은 로컬 지점의 비용입니다.아무리 아름답더라도 예외가 아닌 일반적인 제어 흐름으로 사용해야한다는 의미는 아닙니다.당신은 최적화 컴파일러의 자비에 있습니다!예외적 빈도가 예외적 인 경우에만 예외적 인 경우에만 사용해야합니다.상각하다실제 예외를 제기하는 불길한 비용.

  6. 낙관적 인 규칙은10 ^ -4예외 빈도는 예외적입니다. 물론 이는 예외 자체의 무거운 가중치, 예외 처리기에서 취한 정확한 조치 등에 달려 있습니다.

단점은 예외가 발생하지 않을 때 비용을 지불하지 않기 때문에 예외적 인 조건이 충분하지 않은 경우 예외 처리가if때마다. 전체 게시물은 읽을만한 가치가 있습니다.


37

불행히도 내 대답은 여기에 게시하기에는 너무 길다. 여기에 요약 해 보겠습니다.http://www.fuwjax.com/how-slow-are-java-exceptions/껄끄 러운 세부 사항을 위해.

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

Sentinel 값은 성공의 경우 하나의 클래스를 반환하려는 시도이며 실패한 경우 하나의 클래스를 반환하려는 시도입니다. 예외를 던지기보다는 예외를 반환하는 것으로 거의 생각할 수 있습니다. 이를 위해서는 성공 객체와 공유 상위 클래스가 필요하고 "instanceof"검사를 수행하고 성공 또는 실패 정보를 얻기 위해 몇 개의 캐스트를 수행해야합니다.

타입 안전성의 위험이있을 때 Sentinel 값은 예외보다 빠르지 만 대략 2 배 정도의 값으로 나타납니다. 이제는 그렇게 많이 느껴질 수도 있지만 2 배는 구현 차이의 비용 만 다루고 있습니다. 실제적으로 실패 할 수있는 방법은이 페이지의 샘플 코드 에서처럼 몇몇 산술 연산자보다 훨씬 더 흥미 롭기 때문에 요인은 훨씬 더 낮습니다.

결과 래퍼는 타입 안전을 전혀 희생하지 않습니다. 성공 및 실패 정보를 단일 클래스로 래핑합니다. 그래서 "instanceof"대신 성공과 실패 객체 모두에 대해 "isSuccess ()"와 getter를 제공합니다. 그러나 결과 객체는 대략 2x입니다.느린예외를 사용하는 것보다. 매번 새로운 래퍼 객체를 만드는 것은 때로는 예외를 던지는 것보다 훨씬 비쌉니다.

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

예외는 센티널보다 안전하고 결과 객체보다 빠르며 둘 중 하나보다 덜 놀랍습니다. try / catch가 if / else를 대체한다고 제안하는 것은 아니지만 예외는 비즈니스 로직에서도 실패를보고하는 올바른 방법입니다.

즉, 필자가 수행 한 성능에 영향을주는 가장 빈번한 두 가지 방법은 불필요한 객체와 중첩 루프를 만드는 것입니다. 예외를 만들거나 예외를 만들지 선택할 수있는 경우 예외를 만들지 마십시오. 때때로 예외를 작성하거나 항상 다른 오브젝트를 작성할 수있는 선택 사항이있는 경우 예외를 작성하십시오.


  • 필자는보고없이 실패를 확인하는 제어 구현과 비교하여 세 가지 구현의 장기 성능을 테스트하기로 결정했습니다. 이 프로세스의 실패율은 약 4 %입니다. 테스트를 반복하면 전략 중 하나에 대해 프로세스가 10000 회 호출됩니다. 각 전략은 1000 번 테스트되고 마지막 900 번은 통계를 생성하는 데 사용됩니다. 평균 시간은 나노 단위입니다 : 통제 338 예외 429 결과 348 센티넬 345 - Fuwjax
  • 그냥 재미를 위해 예외 테스트에서 fillInStackTrace를 비활성화했습니다. 지금은 시간입니다 : 통제 347 예외 351 결과 364 센티넬 355 - Fuwjax
  • Fuwjax, 내가 뭔가를 놓치지 않는 한 (그리고 나는 당신의 블로그 포스트가 아닌 당신의 SO 포스트 만 읽는다는 것을 인정한다) 위의 두 코멘트가 당신의 포스트와 모순되는 것처럼 보인다. 나는 당신의 벤치 마크에서 더 낮은 숫자가 더 낫다고 생각 하나? 이 경우 fillInStackTrace를 사용하여 예외를 생성하면 (기본 및 일반적인 비헤이비어), 설명하는 다른 두 기술보다 성능이 저하됩니다. 내가 뭔가를 놓치고 있습니까, 아니면 실제로 귀하의 게시물을 반증하기 위해 논평을 한 적이 있습니까? - Felix GV
  • @ Fuwjax - & quot; 바위와 어려운 장소 & quot;를 피하는 방법 여기에 선물로 드리는 선택은미리 할당하다"성공"을 나타내는 객체. 일반적으로 공통 실패 사례에 대해 객체를 사전 할당 할 수도 있습니다. 그런 다음 추가 세부 정보를 전달하는 드문 경우에만 새 개체가 만들어집니다. (이것은 정수 "에러 코드"와 OO 등가물이며, 수십 년 동안 존재해온 기술인 마지막 에러의 세부 사항을 얻기위한 별도의 호출이다.) - ToolmakerSteve
  • @ Fuwjax 그래서 예외를 던지면 계정에 의해 객체가 생성되지 않습니까? 그 추론을 이해하는지 모르겠다. 예외를 던지거나 결과 객체를 반환하는지 여부에 관계없이 객체를 만듭니다. 이러한 의미에서 결과 객체는 예외를 throw하는 것보다 느리지 않습니다. - Matthias

17

나는 주어진 답을 확장했다.@ 메키@incarnate @incarnateJava 용 stacktrace를 작성하지 않아도됩니다.

Java 7 이상에서는Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace). 하지만 자바 6의 경우이 질문에 대한 내 대답

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

Java 1.6.0_45, Core i7, 8GB RAM의 출력 :

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

따라서 예외를 던지는 메소드에 비해 값을 리턴하는 메소드가 더 빠릅니다. IMHO, 우리는 성공적인 & amp; 모두에 대한 반환 유형을 사용하여 명확한 API를 설계 할 수 없습니다. 오류가 발생합니다. stacktrace없이 예외를 throw하는 메서드는 일반적인 예외보다 4-5 배 빠릅니다.

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

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

7

이 주제가 관련되어 있는지 모르겠지만, 한 번 현재 스레드의 스택 추적에 의존하는 트릭을 구현하기를 원했습니다. 인스턴스 이름이 인스턴스화 된 클래스 내부에서 트리거되는 메소드의 이름을 찾고 싶습니다 (예를 들어, 아이디어가 미친, 나는 그것을 완전히 포기했다). 그래서 나는 그 부름을 발견했다.Thread.currentThread().getStackTrace()~이다.매우천천히 (네이티브 때문에)dumpThreads내부적으로 사용하는 방법).

그래서 자바Throwable이에 상응하는 방식으로 네이티브 메소드가있다.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

알다시피, 유일한 차이점은method3method4그들은 다른 종류의 예외를 던지는 것입니다. Yeap,method4여전히 느리다.method1method2그러나 그 차이는 훨씬 더 수용 가능하다.


7

첫 번째 기사에서는 호출 스택을 탐색하고 스택 추적을 값 비싼 파트로 만드는 작업을 언급하고 두 번째 기사에서는이 작업을 설명하지 않지만 이것이 객체 작성에서 가장 비용이 많이 드는 부분이라고 생각합니다. 존 로즈는예외 처리 속도를 높이기위한 다양한 기술을 설명하는 기사. (예외의 사전 할당 및 재사용, 스택 트레이스가없는 예외 등)

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

자바의 마이크로 벤치 마크는 놀랍게도 옳다. (나는 말했어.) 특히 JIT 영역에 들어서면 예외를 사용하는 것이 현실에서 "반환"보다 빠르다는 것을 정말로 의심한다. 예를 들어, 테스트에서 스택 프레임이 2 ~ 5 개 정도 있다고 생각합니까? 이제 코드가 JBoss에서 배포 한 JSF 구성 요소에 의해 호출 될 것이라고 가정 해보십시오. 이제는 여러 페이지 길이의 스택 추적이있을 수 있습니다.

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


6

필자는 JVM 1.5로 몇 가지 성능 테스트를 수행했으며 예외 사용은 적어도 2 배 느려졌습니다. 평균적으로 : 예외적 인 경우 세 배의 작은 메소드 실행 시간이 3 배 이상 증가했습니다. 예외를 잡아야했던 아주 작은 루프는 자체 시간이 2 배 증가했습니다.

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

예외는 분명히 있어야합니다.아니자주 호출되는 것에 사용하십시오. 1 초에 수천 건의 예외를 던지면 거대한 병목이 발생합니다.

예를 들어 "Integer.ParseInt (...)"를 사용하여 매우 큰 텍스트 파일의 모든 잘못된 값을 찾습니다. 아주 나쁜 생각입니다. (나는이 유틸리티 방법을 보았다.죽이다생산 코드의 성능)

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

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


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

6

한동안 필자는 (1) Integer.parseInt ()를 호출하여 예외를 catch하거나 (2) 문자열을 정규식과 일치시키고 parseInt ()를 호출하여 두 가지 방법을 사용하여 문자열을 int로 변환하는 상대적 성능을 테스트하는 클래스를 작성했습니다. 매치가 성공했을 때에 만. 나는 가장 효율적인 방법으로 정규 표현식을 사용했다. (예를 들어, 루프를 끼우기 전에 Pattern과 Matcher 객체를 만든다.) 예외로부터 stacktraces를 출력하거나 저장하지 않았다.

만 문자열의 목록을 보려면 모든 유효한 숫자 인 경우 parseInt () 접근 방식이 정규식 접근 방식보다 네 배 빠릅니다. 그러나 문자열의 80 % 만 유효하면 정규식은 parseInt ()보다 두 배 빠릅니다. 예외가 throw되어 시간의 80 %가 잡히는 것을 의미하는 20 %가 유효하면 정규식은 parseInt ()보다 약 20 배 빠릅니다.

정규식 접근법이 유효한 문자열을 두 번 처리하는 것을 고려하면 결과에 놀랐습니다. 한 번 일치하고 parseInt ()를 다시 처리합니다. 하지만 예외를 던지고 잡는 것 이상으로 보상받습니다. 이러한 상황은 현실 세계에서 자주 발생하지는 않지만 예외적 인 기술을 사용해서는 안됩니다. 그러나 사용자 입력이나 그와 같은 것을 검증하는 것이라면 반드시 parseInt () 접근법을 사용하십시오.


  • 어느 JVM을 사용 했습니까? 그것은 여전히 sun-jdk 6에서 느린 것입니까? - Benedikt Waldvogel
  • 나는 그것을 파헤 치고 그 대답을 제출하기 전에 JDK 1.6u10 하에서 다시 실행했다. 그리고 그것들은 내가 게시 한 결과들이다. - Alan Moore
  • 이것은 매우 유용합니다! 감사. 내 일반적인 유스 케이스의 경우 사용자 입력을 구문 분석해야한다.Integer.ParseInt()) 나는 그것을 기대한다.대부분의 시간사용자 입력이 정확할 것입니다. 따라서 사용 사례에 대해 가끔 예외가 발생하는 것처럼 보이는 것이 좋습니다. - markvgti

3

예외를 throw하는 것이 느리지는 않지만 정상적인 프로그램 흐름에 대해 예외를 throw하는 것은 여전히 바람직하지 않습니다. GOTO와 비슷한이 방식으로 ...

나는 그것이 정말로 질문에 대답하지 않는다고 생각한다. 느린 예외를 던지는 '일반적인'지식은 이전 Java 버전 (<1.4)에서는 사실이었습니다. 예외를 만들려면 VM이 전체 스택 추적을 작성해야합니다. VM이 그 이후로 속도를 높이기 위해 많은 변화가 있었으며 이것은 개선 된 영역 중 하나 일 것입니다.


  • "정상적인 프로그램 흐름"을 정의하는 것이 바람직 할 것이다. 비즈니스 프로세스 오류로 확인 된 예외를 사용하고 복구 할 수없는 오류에 대해서는 확인되지 않은 예외를 사용하는 것에 대해 많은 부분이 기술되었으므로 비즈니스 로직의 오류가 여전히 정상적인 흐름으로 간주 될 수 있습니다. - Spencer Kormos
  • @Spencer K : 이름에서 알 수 있듯이 예외는 예외적 인 상황이 발견되었음을 의미합니다 (파일이 사라지고 네트워크가 갑자기 닫혔습니다 ...). 이것은 상황이 예상치 못한 것임을 의미합니다. 상황이 발생할 것으로 예상되는 경우 예외를 사용하지 않습니다. - Mecki
  • @ 메키 : 맞아. 나는 최근에 이것에 대해 누군가와 토론을했다 ... 그들은 Validation 프레임 워크를 작성 중이며 검증 실패의 경우 예외를 던지고있다. 나는 이것이 아주 일반적 일 것이므로 나쁜 생각이라고 생각한다. 메소드가 ValidationResult를 리턴하는지보십시오. - user38051
  • 제어 흐름 측면에서 예외는 다음과 유사합니다.break또는return, 아닌goto. - Hot Licks
  • 수많은 프로그래밍 패러다임이 있습니다. 그 의미가 무엇이든간에 단일 '정상적인 흐름'이 될 수는 없습니다. 기본적으로 예외 메커니즘은 현재 프레임을 빠르게 떠나 특정 시점까지 스택을 푸는 방법 일뿐입니다. "예외"라는 단어는 "예상치 못한"특성을 의미하지 않습니다. 빠른 예 : 라우팅 환경에 따라 특정 상황이 발생할 경우 웹 응용 프로그램에서 404를 "버리는"것이 매우 자연 스럽습니다. 논리가 예외로 구현되지 않는 이유는 무엇입니까? 반 패턴은 무엇입니까? - incarnate

3

HotSpot은 모든 코드가 인라인되어있는 한 시스템 생성 예외에 대한 예외 코드를 제거 할 수 있습니다. 그러나 명시 적으로 생성 된 예외 및 제거되지 않은 예외는 스택 추적을 만드는 데 많은 시간을 소비합니다. 보수fillInStackTrace이것이 성능에 어떤 영향을 줄 수 있는지 확인하십시오.


3

Java 및 C #의 예외 성능은 많이 필요합니다.

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

그러나 컴퓨터 과학자로서 우리는이 문제 상태에 반항해야합니다. 함수를 작성하는 사람은 종종 얼마나 자주 호출되는지 또는 성공 또는 실패가 더 많은지 여부를 모릅니다. 발신자에게만이 정보가 있습니다. 예외를 피하려고하면 명확하지 않은 예외적 인 버전 만있는 명확하지 않은 API idoms로 이어지고, 다른 경우에는 빠르지 만 귀찮은 반환 값 오류가 발생하며, 다른 경우에는 둘 다로 끝납니다 . 라이브러리 구현자는 API의 두 가지 버전을 작성하고 유지해야 할 수 있으며 호출자는 각 상황에서 사용할 두 가지 버전을 결정해야합니다.

이것은 일종의 혼란입니다. 예외가 더 나은 수행 능력을 가지면, 우리는 이러한 멍청한 관용구를 피할 수 있고, 구조화 된 오류 반환 시설로 사용되기 위해 예외를 사용할 수 있습니다.

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

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

public class 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-TIMES 느립니다.

이것은 아마 우리의 예외 구현에서 잘못된 절충을하고 있음을 나에게 암시한다. 비용이 많이 드는 스토킹을 피하거나 컴파일러가 지원하는 반환 값 확인으로 완전히 돌리면 예외가 더 빨리 발생할 수 있습니다. 그것들이 만들어 질 때까지 우리는 코드를 빨리 실행하기를 원할 때 그것들을 피하면서 붙어 있습니다.


2

다만, Integer.parseInt를 다음과 같은 방법으로 비교해 봅시다. 예외를 throw하는 대신 파싱 할 수없는 데이터의 경우 기본값을 반환합니다.

  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 번 구문 분석)를 파싱하려고하면 성능 차이가 필수적입니다.


0

@Mecki의 대답을 변경하여 method1이 호출 메소드의 부울 및 검사를 반환하도록했습니다. 예외를 아무 것도 대체 할 수 없기 때문입니다. 두 번 실행 한 후에도 method1은 여전히 가장 빠른 방법 또는 빠른 방법 2였습니다.

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

// 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

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

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/break돌아 오는 중booleanC에서 메소드 호출과 같은 플래그)는 애플리케이션 빈도에 따라 애플리케이션의 성능을 10 배에서 100 배까지 향상시킵니다.


-3

예외 속도와 데이터를 프로그래밍 방식으로 비교하는 것에 대한 제 의견입니다.

많은 클래스에는 가치있는 변환기 (스캐너 / 파서), 존중되고 잘 알려진 라이브러리도 있습니다.)

보통 형태가있다.

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

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

stacktrace를 터미널에 인쇄하지 않고 파일이나 비슷한 것으로 저장하면 catch 블록은 다른 코드 블록보다 더 이상 작업하지 않습니다. 그래서, "throw new my_cool_error ()"가 그렇게 느린 이유를 상상할 수 없습니다.

좋은 질문이며이 주제에 대한 자세한 정보를 기다리고 있습니다!


  • 실제로 스택 트레이스에 대한 정보를 캡처하지 않으면 예외가 발생합니다. - Jon Skeet

연결된 질문


관련된 질문

최근 질문