20

이 질문에는 이미 답변이 있습니다.

자바에서 값 비싼 예외 던지기 및 처리 방법을 아십니까?

우리 팀에서 실제 예외 비용에 대해 여러 번 논의했습니다. 일부는 가능한 한 자주 피하고 일부는 예외를 사용하여 성능 저하가 과대 평가된다고 말합니다.

오늘 저는 소프트웨어에서 다음 코드 조각을 발견했습니다.

private void doSomething()
{
    try
    {
      doSomethingElse();
    }
    catch(DidNotWorkException e)
    {
       log("A Message");
    }
    goOn();
}
private void doSomethingElse()
{
   if(isSoAndSo())
   {
      throw new DidNotWorkException();
   }
   goOnAgain();
}

이것에 비해 성능은 어떻습니까?

private void doSomething()
{
    doSomethingElse();
    goOn();
}
private void doSomethingElse()
{
   if(isSoAndSo())
   {
      log("A Message");
      return;
   }
   goOnAgain();
}

코드 미학이나 다른 것에 관해서는 이야기하고 싶지 않습니다. 단지 런타임 문제 일뿐입니다! 실제 경험 / 측정이 있습니까?


10 답변


6

나는 예외에 관해 읽는 것을 귀찮게하지 않았다. 그러나 당신의 수정 된 코드로 매우 빠른 테스트를하고 나는 예외 상황이 부울의 경우보다 상당히 느리다는 결론에 도달했다.

나는 다음과 같은 결과를 얻었다 :

Exception:20891ms
Boolean:62ms

이 코드에서 :

public class Test {
    public static void main(String args[]) {
            Test t = new Test();
            t.testException();
            t.testBoolean();
    }
    public void testException() {
            long start = System.currentTimeMillis();
            for(long i = 0; i <= 10000000L; ++i)
                    doSomethingException();
            System.out.println("Exception:" + (System.currentTimeMillis()-start) + "ms");
    }
    public void testBoolean() {
            long start = System.currentTimeMillis();
            for(long i = 0; i <= 10000000L; ++i)
                    doSomething();
            System.out.println("Boolean:" + (System.currentTimeMillis()-start) + "ms");
    }

    private void doSomethingException() {
        try {
          doSomethingElseException();
        } catch(DidNotWorkException e) {
           //Msg
        }
    }
    private void doSomethingElseException() throws DidNotWorkException {
       if(!isSoAndSo()) {
          throw new DidNotWorkException();
       }
    }
    private void doSomething() {
        if(!doSomethingElse())
            ;//Msg
    }
    private boolean doSomethingElse() {
       if(!isSoAndSo())
          return false;
       return true;
    }
    private boolean isSoAndSo() { return false; }
    public class DidNotWorkException extends Exception {}
}

나는 어리석게도 내 코드를 충분히 읽지 못했고 이전에 버그가 있었다. (얼마나 당황 스럽다.) 누군가가이 코드를 세 번 검사 할 수 있다면 나는 그것을 매우 숙고하게 될 것이다.

내 사양은 다음과 같습니다.

  • 1.5.0_16에서 컴파일 및 실행
  • 썬 JVM
  • WinXP SP3
  • 인텔 센트리노 듀오 T7200 (2.00Ghz, 977Mhz)
  • 2.00 GB 램

내 의견으로는 예외가 아닌 메소드는 doSomethingElse에서 로그 오류를주지 않지만 대신 호출 코드가 실패를 처리 할 수 있도록 부울을 리턴한다는 것을 알아야한다. 실패 할 수있는 영역이 여러 개있는 경우 오류를 내부에 기록하거나 예외를 throw해야 할 수 있습니다.


  • 당신은 확실 해요, 당신은 개인 부울로 테스트했습니다 isSoAndSo () {return그릇된; }이 경우 귀하의 결과에 놀랄 것입니다. - Kai Huppmann
  • 오 세상에, 나는 그 피의 날들 중 하나를 가지고 있습니다. (예외는 예외가 아니라면 2 배의 차이가 있습니다. - Henry B
  • 문제 없음 & amp; 이 질문에 대한 귀하의 모든 노력에 감사드립니다! - Kai Huppmann
  • 문제 없습니다. 다행이었습니다. - Henry B
  • 이 비교는 불공평하다. 왜냐하면 a) "if" doSomethingElse ()의 모든 컴파일러는 최적화 된 컴파일러에서 최적화되었습니다. b) 최신 CPU의 분기 예측 기능으로 인해 눈에 띄지 않습니다. 일반적으로 조건은 대부분 거짓이며 예외적 인 상황에서는 참입니다. CPU의 분기 예측은 조건이 false로 평가되고 else 분기에 대한 명령어를로드하지만 조건이 참 (예외적 인 상황) 인 것으로 판단되면 파이프 라인을 비우고 그 다음 분기를로드해야합니다 명령문은 수십 CPU주기를 소비 할 수 있습니다. - Stefan Majewsky

11

예외는 무료가 아닙니다 ... 그래서 그들은 비쌉니다 :-)

그 책효과적인 자바이것을 상세하게 다룹니다.

  • 항목 39 예외적 인 경우에만 예외를 사용하십시오.
  • 항목 40 복구 가능한 조건에 대한 예외 사용

필자는 예외로 인해 특정 VM 및 OS 콤보를 사용하여 자신의 컴퓨터에서 테스트 케이스를 70 배 느리게 튜닝하는 결과를 얻었습니다.


  • 논리 오류 - 그들은 단지 싸울 수 있습니다! - alex
  • 아니 ... 그들은 아닙니다 :-) (그러나 나는 인용문을 찾아야 만했습니다 ...) - TofuBeer
  • 예외는 실제로 비싸다. 예외의 발생이 통화의 1 %에서만 발생하는 벤치 마크를 만들었습니다. 이는 다소 현실적인 사례입니다. 이러한 상황에서도 성과 처벌은 70 배가 아니지만 매우 높습니다. 소스 코드 및 벤치 마크 결과보기이리. - Richard Gomes

11

예외를 throw하는 가장 느린 부분은 다음과 같습니다.스택 추적 작성.

예외를 미리 작성하여 다시 사용하는 경우 JIT는이를 "기계 수준의 고토. "

귀하의 질문에있는 코드가 정말로 엄격한 루프에 있지 않는 한, 그 차이는 무시할 수있을 것입니다.


  • 예외 생성자가 super (msg, cause, / * enableSuppression * / false, / * writableStackTrace * / false)를 호출하면 예외를 생성하지 않고 새 예외를 만들 수 있습니다. - giampaolo

8

예외에 대한 느린 부분은 스택 추적을 구축하는 것입니다 (java.lang.Throwable), 스택 깊이에 따라 다릅니다. 던지는 것은 그다지 느리지 않습니다.

예외를 사용하여 오류를 알립니다. 그러면 성능에 미치는 영향은 무시할 수 있으며 스택 추적은 실패의 원인을 정확하게 지적하는 데 도움이됩니다.

제어 흐름 (권장하지 않음)에 대한 예외가 필요하고 프로파일 링에서 병목 현상 인 예외가 표시되면 재정의하는 Exception 하위 클래스를 만듭니다fillInStackTrace()빈 구현. 선택적으로 (또는 추가적으로) 하나의 예외를 인스턴스화하고 필드에 저장하고 항상 동일한 인스턴스를 던집니다.

다음은 마이크로 벤치 마크에 하나의 간단한 메소드를 추가하여 스택 추적이없는 예외를 보여줍니다 (그래도 결함이있다.)에서대답을 수락했다.:

public class DidNotWorkException extends Exception {
  public Throwable fillInStackTrace() {
      return this;
  }
}

JVM을 사용하여 실행-server모드 (Windows 7의 경우 버전 1.6.0_24)의 결과는 다음과 같습니다.

Exception:99ms
Boolean:12ms

Exception:92ms
Boolean:11ms

그 차이는 실제로 무시할만큼 작습니다.


  • @Dave Jarvis - 두 개의 별도 실행 및 대표 결과 인 경우 900 % 실행 시간을 "거의 차이가 없음"이라고 부르지 않습니다. Hello World를 모두 코딩하면 예외가 발생합니다. . - im so confused
  • 그것은 오히려 차이가 아니라 오히려 10의 요인처럼 보입니다. 이것을 여러 곳에서 사용하면 많은 상처를 입을 것입니다. - Arne Babenhauserheide

4

이것은 본질적으로 JVM에 따라 다르므로, 조언이 제공된 것은 맹목적으로 신뢰하지 말고 실제로 상황을 측정해야합니다. 거친 아이디어를 얻으려면 "100 만 건의 예외를 throw하고 System.currentTimeMillis의 차이점을 인쇄하십시오"를 만드는 것이 어렵지 않습니다.

여러분이 열거 한 코드 조각을 위해 필자는 나중에 원래의 작성자에게 예외를 던지는 이유를 철저히 문서화하도록 요구할 것입니다. 나중에 "유지"하는 것이 가장 중요하지 않은 "최소한의 놀라움의 길"이 아니기 때문입니다.

(당신이 복잡한 방식으로 무언가를 할 때마다, 독자가 불필요한 작업을 왜했는지를 이해하기 위해 왜 독자가 그것을 마치 평범한 방식이 아닌 그것을 좋아 하는지를 이해하기 위해서 - 왜 그 일을 정당화해야 하는가? 그곳에는 이유가 있어야만하는 것처럼 그렇게 행해졌습니다).

예외는 매우 유용한 도구이지만 필요한 경우에만 사용해야합니다.


  • JVM 관련 +1. 예를 들어 Java 1.4에서 try-catch 블록의 성능에 큰 문제가있었습니다 (주로 이후 릴리스에서 수정 됨). - cletus
  • +1 JVM 특정 및 실제로 그것을 테스트합니다. 자바는 속도면에서 먼 길을왔다. - Kiv

3

나는 실제 측정이 없지만 예외를 던지면 더 비쌉니다.

좋아,이 .NET 프레임 워크에 관한 링크입니다,하지만 같은 자바 적용됩니다 생각 :

예외 & amp; 공연

즉, 당신이 그들을 사용할 때 주저해서는 안된다.적당한. 즉, 흐름 제어에 사용하지 말고 예외적 인 상황이 발생할 때 사용하십시오. 네가 일어날 것을 기대하지 않은 것.


  • 완전히 예외적입니다. 예외적 인 경우 예외를 사용하면 정상적으로 처리됩니다. 초당 여러 번 던지면 흐름 제어를 위해 설계되지 않았기 때문에 속도가 느려집니다. - ojrac
  • 흠, 왜 다운 그레이드? 답이 다운 그레이드되면 왜 동기를 부여해야합니다. - Frederik Gheysels

3

우리가 필요로하는 곳 (예외적 인 조건)에서 예외를 사용하는 것으로 고집한다면, 당신이 지불 할 수있는 성과 위약보다 이점이 훨씬 크다고 생각합니다. 비용은 실제로 실행중인 응용 프로그램에서 예외가 발생하는 빈도의 함수이기 때문에 나는 말할 수 있습니다.

예를 들어, 실패가 예기치 않은 것이나 치명적인 결과가 아닌 것처럼 보입니다. 따라서 메소드는 예외를 사용하기보다는 성공 상태를 알리기 위해 실제로 bool을 반환해야하므로 규칙적인 제어 흐름에 포함됩니다.

필자가 참여한 성능 향상 작업에서 예외 비용은 상당히 낮습니다. 일반적으로 반복되는 작업의 복잡성을 개선하는 데 훨씬 더 많은 시간을 할애해야합니다.


3

모든 응답을 주셔서 감사합니다.

나는 마침내 Thorbjørn의 제안을 따랐고 성능을 직접 측정하는 작은 테스트 프로그램을 작성했습니다. 결과는 다음과 같습니다.아니두 가지 변형의 차이 (성능 문제).

코드 미학이나 무언가, 즉 예외의 의도 등을 묻지는 않았지만 대부분의 사람들이 그 주제를 다루었습니다. 그러나 현실적으로 상황이 항상 명확하지는 않습니다 ... 고려중인 상황에서 예외가 발생한 상황이 예외적 인 것으로 보이는 오래전에 코드가 생성되었습니다. 오늘날 라이브러리는 다르게 사용되며 여러 응용 프로그램의 동작과 사용이 변경되어 테스트 커버리지가 좋지 않지만 코드는 여전히 너무 느립니다 (성능에 대해 질문 한 이유입니다). 그런 상황에서 나는 A에서 B로 변화 할 좋은 이유가 있어야한다고 생각한다. 그것은 나의 생각으로는 "예외가 만들어진 것이 아니다!"가 될 수 없다는 것이다.

로깅 ( "A message")은 (다른 모든 일들에 비해)대단히비싼, 그래서 나는 이것을 제거 할 것이라고 생각한다.

편집하다:

테스트 코드는 메소드에 의해 호출 된 원래 게시물의 코드와 동일합니다.testPerfomance()둘러싸인 루프에서System.currentTimeMillis()실행 시간을 얻으려면 - 호출하지만 ... :

나는 지금 테스트 코드를 검토하고 다른 모든 것 (로그 문)을 돌리고 이전보다 100 배 반복하여 원래 게시물의 A 대신 B를 사용할 때 백만 호출에 대해 4.7 초를 절약한다는 것을 알게되었습니다. 론이 말했듯이fillStackTrace가장 비싼 부분입니다 (+1). 덮어 쓰면 거의 같은 (4.5 초)를 절약 할 수 있습니다 (필요없는 경우). 결국 코드가 시간당 1000 번 호출되고 그 측정치가 그 시간에 4.5 밀리 초를 절약 할 수 있다는 것을 보여주기 때문에 내 경우에는 거의 제로 차이입니다.

그래서, 위의 첫 번째 답변 부분은 다소 오해의 소지가 있었지만 리팩터링의 비용 편익을 균형 잡기 위해 내가 말한 것은 사실입니다.


  • 당신의 테스트에서 어떻게 예외가 던져 졌습니까? 매번 또는 거의 몇 번? - TofuBeer
  • 테스트 코드를 게시 할 수 있습니까? JVM과 OS는 물론입니까? (대부분의 JVM 공급 업체가 예외 사례 최적화를 방해하지 않으므로 시간이 0 또는 매우 작다는 사실을 믿기 어렵습니다. - TofuBeer
  • 오 ... 그리고 이것이 프로덕션 코드에서 속도의 변화가 없다는 것을 알았다면 ... 1) 메소드가 호출 될 때마다 예외를 던지는 이유는 무엇입니까? 2) 얼마나 자주 전화를 받는지 (시간이별로 없거나 시간이 많이 걸리지 않는 경우) - TofuBeer
  • 5 초 이상 실행 한 루프에서 테스트하지 않은 경우 (그리고 백만 회 정도의 반복을 실행하는 경우)이 코드를 올바르게 테스트하지 않은 것입니다. SO를 검색하여 "스톱워치"를 찾습니다. 스톱워치 벤치 마크 작성에 관한 조언. - Lawrence Dol
  • 한 시간에 천 번씩은 눈에 띄지 않습니다. 효과적인 자바 책 (텍스트가되도록 Google 도서 사이트에 대한 링크)의 예제를 읽으면 훨씬 더 운명적 인 버전 (사람들이 프로덕션 코드에서 사용 했음)을 볼 수 있습니다. - TofuBeer

0

나는 당신이 약간 잘못된 각도에서 이것을 요구하고 있다고 생각합니다. 예외는 신호에 사용되도록 설계되었습니다.예외적 인 경우, 그리고프로그램 흐름 메커니즘그럴 경우. 따라서 질문해야 할 질문은 코드의 "논리"가 예외를 요구하는지 여부입니다.

예외는 일반적으로 의도 된 용도로 충분히 잘 수행되도록 설계됩니다. 그들이 병목 현상이있는 방식으로 사용된다면 무엇보다도 그 프로그램은 단지 "잘못 된 것"즉, 완전히 멈춘 것으로 나타났습니다. 즉, 근본적으로 프로그램이 무엇인지디자인문제보다는공연문제.

반대로 예외가 "올바른 작업에 사용됨"으로 나타나면 아마 OK를 수행 할 수도 있습니다.


0

구문 1과 2를 실행하려고 할 때 예외가 발생하지 않는다고 가정 해 봅시다. 두 샘플 코드 사이에 성능 히트가 있습니까?

그렇지 않다면DoSomething()방법은웃었다작업량 (다른 방법에 대한 호출로드 등)?

1:

try
{
   DoSomething();
}
catch (...)
{
   ...
}

2 :

DoSomething();


  • 자바에서 try는 비용을 무료로 던진다. (실제로는 무료는 아니지만 ... 내가 말한 것을 잊어 버리자 :-) 예외가 발생하지 않는다면 속도 위반은 없다. . 로직의 일부는 흐름 제어에 사용해서는 안되기 때문에 최적화되어 있지 않습니다. 일반적인 경우가 아닌 비정상적인 경우입니다. - TofuBeer

연결된 질문


관련된 질문

최근 질문