2008-10-02 6 views
131

예외를 던지거나하지 않을시기에 대한 토론은 원하지 않습니다. 나는 간단한 문제를 해결하고 싶다. 99 %는 예외를 던지지 않는 것에 대한 논쟁이 느리게 진행되는 반면 다른 쪽은 (벤치 마크 테스트에서) 속도가 문제가 아니라고 주장합니다. 나는 수많은 블로그, 기사 및 게시물을 읽었습니다. 그래서 어떤가?.NET 예외는 얼마나 느립니까?

답변의 링크 : Skeet, Mariani, Brumme.

+12

거짓말, 거짓 거짓말 및 벤치 마크가 있습니다. :) – gbjbaanb

+0

불행히도, 여기 몇 가지 높은 대답은 "느린 예외는 무엇입니까?"라는 질문을 놓친 것입니다. 특히 사용 빈도에 대한 주제를 피하기 위해 구체적으로 질문했습니다. Windows CLR에서 예외는 반환 값보다 750 배 느립니다. –

답변

189

나는 "느리지"않은 쪽, 또는 더 정확하게 "정상적인 사용에서 피할 가치가 있도록 충분히 느려지지 않을 것"입니다. 나는 그것에 대해 두 shortarticles을 썼다. 벤치마킹 측면에 대한 비판은 "실생활에서는 더 많은 스택이 있어야 캐시를 날려 버릴 수 있습니다"라고 비판합니다 -하지만 오류 코드를 사용하면 스택을 따라 작업 할 수 있습니다 또한 캐시를 날려 버리는 것이므로 특히 좋은 주장으로 보지 못합니다.

그냥 분명하게 - 논리적이지 않은 예외 사용은 지원하지 않습니다. 예를 들어 int.TryParse은 사용자의 데이터를 변환하는 데 전적으로 적합합니다. "컴퓨터가 생성 한 파일을 읽을 때 적절합니다."실패한 파일은 의도 한 형식이 아니며, 그 밖의 다른 것을 알지 못하기 때문에이 파일을 처리하려고하지 않습니다. "

"합리적인 경우에만"예외를 사용하면 예외로 인해 성능이 크게 저하 된 응용 프로그램을 본 적이 없습니다. 기본적으로 중요한 문제가 발생하지 않는 한 예외가 자주 발생하지 않아야합니다. 또한 중요한 정확성 문제가있는 경우 성능이 가장 큰 문제는 아닙니다.

+3

우수 기사. 당신이 말한대로 일이 잘못 갈 때 – MusiGenesis

+2

불행하게도, 사람들이 이야기하는 예외 사소한 '올바른'기능을 사용할 무료이며, 그들은, 사용되어야한다 - '특별한'상황 – gbjbaanb

+4

예, 사람들은 확실히 성능이가 있다는 것을 알고 있어야에서 예외를 부적절하게 사용하는 것과 관련된 비용. 나는 그들이 적절하게 사용될 때 문제가 아닌 것으로 생각한다. :) –

1

릴리스 모드에서는 오버 헤드가 최소화됩니다.

흐름 제어 (예 : 비 로컬 exit)에 대한 예외를 재귀 적 방식으로 사용하지 않는 한, 그 차이를 알 수 있을지는 의문입니다.

5

내가 아는 논점은 던지는 예외가 나쁘다는 것 자체가 느립니다. 대신, 전통적인 조건부 구조 대신 일반 응용 프로그램 논리를 제어하는 ​​첫 번째 방법으로 throw/catch 구조를 사용합니다.

종종 일반적인 응용 프로그램 논리에서 동일한 작업이 수천/수백만 번 반복되는 루핑을 수행합니다. 이 경우 매우 간단한 프로파일 링 (Stopwatch 클래스 참조)을 사용하면 간단한 if 문이 상당히 느려질 수 있다고 말하는 대신 예외를 throw하는 것을 직접 볼 수 있습니다.

실제로 Microsoft의 .NET 팀은 .NET 2.0의 TryXXXXX 메소드를 많은 기본 FCL 유형에 도입했는데, 이는 고객이 응용 프로그램의 성능이 매우 느리다고 불평했기 때문입니다.

많은 경우 고객이 루프에서 값의 유형 변환을 시도 했으므로 각 시도가 실패했습니다. 변환 예외가 throw 된 다음 예외 처리기에서 catch 한 다음 예외를 삼켜 루프를 계속했습니다.

Microsoft는 TryXXX 메서드를 사용하여 가능한 이런 성능 문제가 발생하지 않도록 특히이 상황에서 사용하는 것이 좋습니다.

내가 틀릴 수도 있지만, 읽은 "벤치 마크"의 진실성에 대해 확실하지 않은 것처럼 들립니다. 간단한 해결책 : 직접 사용해보십시오.

+0

나는 그 internatlly 그 "시도"기능뿐만 아니라 예외를 사용하여 생각? – greg

+1

이러한 "Try"기능은 입력 값을 구문 분석하지 못한 경우 내부적으로 예외를 throw하지 않습니다. 그러나 그들은 여전히 ​​ArgumentException과 같은 다른 오류 상황에 대한 예외를 throw합니다. – Ash

+0

나는이 대답이 다른 어떤 것보다이 이슈의 핵심에 더 가깝다고 생각한다. '합리적인 상황에서만 예외를 사용하라'는 말은 실제로 질문에 대답하지 않습니다. 실제 통찰력은 제어 흐름에 C# 예외를 사용하는 것이 일반적인 조건부 구문보다 훨씬 느립니다. 달리 생각하면 용서받을 수 있습니다. OCaml에서 예외는 GOTO와 imperative 기능을 사용할 때 _break_를 구현하는 허용 된 방법입니다. 내 경우에는 꽉 짜인 루프 _int.Parse() _ plus _try/catch_ vs. _int.TryParse() _를 사용하면 성능이 크게 향상되었습니다. –

3

예외와 관련된 성능 문제가 없었습니다. 예외를 많이 사용합니다. 가능한 경우 반환 코드를 사용하지 않습니다. 그들은 나쁜 습관이고, 제 의견으로는 스파게티 코드와 같은 냄새가납니다.

예외를 사용하는 방법은 모두 다음과 같습니다. 리턴 코드 (스택의 각 메소드 호출과 다시 호출)를 사용하면 각 캐치 오버 헤드가 있기 때문에 속도가 느려집니다. /던지다.

그러나 스택의 맨 아래에 던져서 맨 위를 잡으면 (하나의 throw/catch로 리턴 코드의 전체 체인을 대체 함), 모든 값 비싼 조작이 한 번 수행됩니다.

하루가 끝날 때 유효한 언어 기능입니다.

그냥

code at this link (답변 너무 커서)를 실행하십시오 내 지점을 증명합니다. 내 컴퓨터에

결과 :

[email protected]:~/develop/test$ mono Exceptions.exe | grep PM
10/2/2008 2:53:32 PM
10/2/2008 2:53:42 PM
10/2/2008 2:53:52 PM

타임 스탬프는 마지막에 리턴 코드 및 예외의 시작 부분에 출력이다. 두 경우 모두 같은 시간이 걸립니다. 최적화와 함께 컴파일해야한다는 점에 유의하십시오.

2

반환 코드로 비교하면 지옥처럼 느립니다. 그러나 이전 포스터에서 말한 것처럼 정상적인 프로그램 작업을 포기하고 싶지 않아 문제가 발생했을 때 퍼포먼스를 얻을 수 있으며 대다수의 경우 성능은 더 이상 중요하지 않습니다 (예외적으로 예외는로드 블록을 의미 함).

그들은 확실히 오류 코드를 사용하여 가치가있어, 장점은 광대 한 IMO입니다.

27

Chris Brumme을 구현 한 사람에게서 확실한 답이 있습니다. 그는 주제에 대해 excellent blog article (경고 - 매우 긴)을 썼습니다 (경고 2 - 아주 잘 쓰여졌습니다. 끝까지 읽은 다음 작업 후 시간을 보완해야합니다.)

실행 요약 : 그들은 느립니다. 그것들은 Win32 SEH 예외로 구현되므로 일부는 링 0 CPU 경계를 통과합니다! 분명히 현실 세계에서는 이상한 예외가 전혀 발견되지 않을 것입니다.하지만 앱을 제외하고 프로그램 흐름에 사용하면 망치게됩니다. 이것은 우리에게 불쾌감을주는 MS 마케팅 기계의 또 다른 예입니다. 마이크로 소프트 한 대가 오버 헤드가 전혀 발생하지 않았다고 말하는 것을 기억합니다. 이는 완료된 tosh입니다.

는 사실, CLR은 내부적으로도 엔진의 관리되지 않는 부분에 예외를 사용

크리스는 관련 견적을 제공합니다. 그러나 예외가있는 심각한 장기적인 문제가 있으며이 문제는 결정과 함께 결정에 포함되어야합니다.

7

사람들이 던져 질 때만 천천히 말한다는 것에 대해 사람들이 무슨 말을하고 있는지 전혀 모르겠습니다.

편집 : 예외가 발생하지 않는 경우에는 그 새로운 예외() 또는 그런 일을 의미합니다. 그렇지 않으면 예외는 스레드가 일시 중단되고 스택이 이동하게합니다. 이것은 작은 상황에서는 좋을 수도 있지만 트래픽이 많은 웹 사이트에서는 워크 플로 또는 실행 경로 메커니즘으로 예외를 사용하면 성능 문제가 발생할 수 있습니다. 예외는 그 자체로 나쁘지 않고 예외적 인 조건을 표현하는 데 유용합니다.

.NET 응용 프로그램의 예외 워크 플로는 첫 번째 및 두 번째 예외를 사용합니다. 모든 예외의 경우 catch하고 처리하는 경우에도 예외 객체가 여전히 생성되며 프레임 워크는 핸들러를 찾기 위해 스택을 계속 걸어야합니다. 잡는다면 물론 오래 걸릴 것입니다 - 당신은 첫 번째 예외를 잡을 것입니다, 그것을 잡아서, 다시 던져서, 다른 첫 번째 예외를 일으킨 다음 핸들러를 찾지 못하게됩니다. 두 번째 예외. 당신은, 예외의 톤을 던지고있다 다음은 성능과 메모리 문제를 일으키는, 그래서 만약 -

예외는 힙에 객체입니다.

또한, ACE 팀에 의해 쓰여진 "성능 테스트 마이크로 소프트 .NET 웹 응용 프로그램"내 사본에 따라 :

"예외 처리 비용이 CLR은 호출 스택을 통해 재귀하면서 참여 스레드의 실행이 일시 중단됩니다. 올바른 예외 처리기를 찾아서 예외 처리기와 일부 최종 블록이 모두 정상적인 처리를 수행하기 전에 실행할 수있는 기회를 가져야합니다. "

현장에서 저의 경험에 따르면 예외를 줄이면 성능이 크게 향상되었습니다. 물론 성능 테스트 (예 : 디스크 I/O가 실행되거나 쿼리가 몇 초 내에 수행되는 경우)가 중요 할 때 고려해야 할 사항이 있습니다. 그러나 예외를 찾고 제거하는 것이 그 전략의 중요한 부분이되어야합니다.

+1

당신이 작성한 어떤 것도 예외가 던져지면 느려지기도한다는 주장과 모순되지 않습니다. 당신은 그들이 던져지는 상황에 대해서만 이야기했습니다. 예외를 제거하여 "성능을 크게 향상"한 경우 : 1) 실제 오류 조건입니까 아니면 사용자 * 오류였습니까? –

+0

2) 디버거에서 실행 중이십니까? –

+0

던지지 않는 경우 예외로 할 수있는 유일한 방법은 객체로 생성하는 것입니다. 이는 의미가 없습니다. 디버거 아래에 있는지 여부는 중요하지 않습니다. 여전히 느려질 것입니다. 예, 디버거가 부착 된 상태에서 일어날 수있는 갈고리가 있지만 여전히 느립니다. –

4

내 XMPP 서버는 지속적으로 문제가 발생하지 않도록 (예 : 소켓이 연결되어 있는지 확인한 후 데이터를 더 읽으려고 시도하는 등) 큰 속도 향상 (미안하지만 실제로 숫자가 아닌 순전히 관찰)을 얻었습니다. 그들을 피하는 방법 (언급 된 TryX 방법). 이는 약 50 명의 활성 (채팅) 가상 사용자 만있었습니다.

+3

숫자가 유용 할 것입니다. (불행히도 : 디버깅하지 않을 때 소켓 작업과 같은 것이 예외 비용보다 훨씬 중요해야합니다. 완전히 벤치마킹 한 결과라면 실제로 결과를 보려는 것입니다. –

0

catching 예외와 관련된 성능에 대한 간략한 설명입니다.

실행 경로가 'try'블록에 들어가면 마술은 발생하지 않습니다. 'try'명령은 없으며 try 블록을 시작하거나 종료하는 데 비용이 들지 않습니다. try 블록에 대한 정보는 메서드의 메타 데이터에 저장되며이 메타 데이터는 예외가 발생할 때마다 런타임에 사용됩니다. 실행 엔진은 스택에서 try 블록에 포함 된 첫 번째 호출을 찾습니다. 예외 처리와 관련된 모든 오버 헤드는 예외가 발생하는 경우에만 발생합니다.

+1

그러나 exceptions * can * 최적화에 영향을 미칠 수 있음 - 명시 적 예외 핸들러가있는 메소드가 인라인하기가 어렵고 명령어 순서가 그들에 의해 제한됨. –

+0

인라인에 영향을주는 좋은 점, Eamon –

2

그냥이 토론에 내 자신의 최근 경험을 추가 : 줄 위의 기록 무엇을 가장하고, 난 디버거 실행하지 않고, 반복적으로 수행 할 때 매우 느린 던지는 예외를 발견했다. 필자가 작성한 대형 프로그램의 성능을 약 5 줄의 코드를 변경하여 60 % 씩 증가 시켰습니다. 예외를 throw하는 대신 리턴 코드 모델로 전환했습니다. 내가 변경하기 전에 문제의 코드가 수천 번 실행되었으며 잠재적으로 수천 가지 예외가 발생할 수 있음을 인정합니다. 그래서 저는 위의 진술에 동의합니다 : 어떤 중요한 상황이 실제로 잘못 될 때 예외를 던지십시오. "예상 된"상황에서 응용 프로그램 흐름을 제어하는 ​​방법이 아닙니다.

2

그러나 모노는 예외보다 10 배 빠른 예외를 throw합니다.net 독립 실행 형 모드 및 .net 독립 실행 형 모드에서는 .net 디버거 모드보다 60 배 빠른 예외가 throw됩니다. 깊이-8 호출 체인에 대한 Windows CLR에

int c = 1000000; 
int s = Environment.TickCount; 
for (int i = 0; i < c; i++) 
{ 
    try { throw new Exception(); } 
    catch { } 
} 
int d = Environment.TickCount - s; 

Console.WriteLine(d + "ms/" + c + " exceptions"); 
1

, (시험 기계 같은 CPU 모델을 가지고), 예외를 던지고 확인하고 반환 값을 전파하는 것보다 750 배 느립니다. 윈도우 CLR이 Windows Structured Exception Handling이라는 것을 통합하기 때문에

예외이 높은 비용은 (벤치 마크 아래 참조). 이를 통해 예외가 다른 런타임 및 언어로 올바르게 포착되고 throw 될 수 있습니다. 그러나 매우 느립니다. 이 SEH와 통합하지 않기 때문에 (모든 플랫폼) 모노 런타임에서

예외가 훨씬 빠릅니다. 그러나 SEH와 같은 기능을 사용하지 않기 때문에 여러 런타임에 예외를 전달할 때 기능 손실이 발생합니다. 여기

는 Windows CLR에 대한 반환 값 대 예외 내 벤치 마크 결과를 축약된다. 여기
baseline: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 13.0007 ms 
baseline: recurse_depth 8, error_freqeuncy 0.25 (0), time elapsed 13.0007 ms 
baseline: recurse_depth 8, error_freqeuncy 0.5 (0), time elapsed 13.0008 ms 
baseline: recurse_depth 8, error_freqeuncy 0.75 (0), time elapsed 13.0008 ms 
baseline: recurse_depth 8, error_freqeuncy 1 (0), time elapsed 14.0008 ms 
retval_error: recurse_depth 5, error_freqeuncy 0 (0), time elapsed 13.0008 ms 
retval_error: recurse_depth 5, error_freqeuncy 0.25 (249999), time elapsed 14.0008 ms 
retval_error: recurse_depth 5, error_freqeuncy 0.5 (499999), time elapsed 16.0009 ms 
retval_error: recurse_depth 5, error_freqeuncy 0.75 (999999), time elapsed 16.001 ms 
retval_error: recurse_depth 5, error_freqeuncy 1 (999999), time elapsed 16.0009 ms 
retval_error: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 20.0011 ms 
retval_error: recurse_depth 8, error_freqeuncy 0.25 (249999), time elapsed 21.0012 ms 
retval_error: recurse_depth 8, error_freqeuncy 0.5 (499999), time elapsed 24.0014 ms 
retval_error: recurse_depth 8, error_freqeuncy 0.75 (999999), time elapsed 24.0014 ms 
retval_error: recurse_depth 8, error_freqeuncy 1 (999999), time elapsed 24.0013 ms 
exception_error: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 31.0017 ms 
exception_error: recurse_depth 8, error_freqeuncy 0.25 (249999), time elapsed 5607.3208  ms 
exception_error: recurse_depth 8, error_freqeuncy 0.5 (499999), time elapsed 11172.639 ms 
exception_error: recurse_depth 8, error_freqeuncy 0.75 (999999), time elapsed 22297.2753 ms 
exception_error: recurse_depth 8, error_freqeuncy 1 (999999), time elapsed 22102.2641 ms 

그리고

은 .. 코드입니다

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace ConsoleApplication1 { 

public class TestIt { 
    int value; 

    public class TestException : Exception { } 

    public int getValue() { 
     return value; 
    } 

    public void reset() { 
     value = 0; 
    } 

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

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

    public void exception_error(bool shouldfail, int recurse_depth) { 
     if (recurse_depth <= 0) { 
      if (shouldfail) { 
       throw new TestException(); 
      } 
     } 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 = 1000000; 


     // (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; 
       DateTime start_time = DateTime.Now; 
       t.reset();    
       for (i = 1; i < ITERATION_COUNT; i++) { 
        bool shoulderror = (i % EXCEPTION_MOD) == 0; 
        t.baseline_null(shoulderror,recurse_depth); 
       } 
       double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds; 
       Console.WriteLine(
        String.Format(
         "baseline: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms", 
         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; 
       DateTime start_time = DateTime.Now; 
       t.reset();    
       for (i = 1; i < ITERATION_COUNT; i++) { 
        bool shoulderror = (i % EXCEPTION_MOD) == 0; 
        if (!t.retval_error(shoulderror,recurse_depth)) { 
         failures++; 
        } 
       } 
       double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds; 
       Console.WriteLine(
        String.Format(
         "retval_error: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms", 
         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; 
       DateTime start_time = DateTime.Now; 
       t.reset();    
       for (i = 1; i < ITERATION_COUNT; i++) { 
        bool shoulderror = (i % EXCEPTION_MOD) == 0; 
        try { 
         t.exception_error(shoulderror,recurse_depth); 
        } catch (TestException e) { 
         failures++; 
        } 
       } 
       double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds; 
       Console.WriteLine(
        String.Format(
         "exception_error: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms", 
         recurse_depth, exception_freq, failures,elapsed_time));   } 
     } 
    } 
} 


} 
+4

질문의 요점을 놓치지 않고, DateTime 이제는 벤치 마크를 위해 - 경과 시간을 측정하기 위해 설계된 스톱워치를 사용하십시오. 상당히 오랜 시간을 측정하면서 습관에 빠질 가치가 있습니다. 여기에서 문제가되어서는 안됩니다. –

+0

반대로, 질문은 " 예외적 인 경우는 느립니다. " 그 주제가 사실을 모호하게했기 때문에 예외를 던지는 주제를 피하기 위해 예외의 성능은 무엇입니까? –

-1

다른 사람들이 예외가 적절한 때 말하기 어려울 것으로 보인다 사용하는 클래스/함수를 작성. BCL의 유용한 부분 중 일부는 오류를 반환하는 대신 예외를 throw하기 때문에 피봇 (pinvoke)을 위해 도랑을 가야했습니다. 어떤 경우에는 해결할 수 있지만 System.Management 및 Performance Counters 같은 경우에는 BCL에서 예외가 자주 발생하는 루프를 사용해야하는 경우가 있습니다.

라이브러리를 작성 중이며 루프에서 함수를 사용할 수 있고 많은 양의 반복이 발생할 가능성이있는 경우 Try .. 패턴 또는 다른 방법으로 예외 옆에 오류를 표시하십시오 . 그리고 심지어 공유 환경에서 많은 프로세스가 사용하고 있다면 함수가 얼마나 많이 호출 될지 말하기는 어렵습니다. 일이 필요가 스택 추적을보고 가서 무엇이 잘못되었는지 확인하고 다음을 해결하기 위해 너무 뛰어난 때 내 자신의 코드에서

는 예외은 던져진다. 그래서 나는 BCL의 일부를 다시 작성하여 예외 대신 Try .. pattern을 기반으로 오류 처리를 사용했습니다.

+2

이것은 포스터의 "* 예외를 던지거나하지 말아야 할 것에 대한 토론을 원하지 않습니다"라는 문구에 어울리지 않는 것 같습니다. – hrbrmstr