Programing

.NET 예외는 얼마나 느립니까?

lottogame 2020. 6. 17. 20:27
반응형

.NET 예외는 얼마나 느립니까?


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

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


나는 "느리게하지 않는"편에있다.보다 정확하게는 "정상적으로 사용하는 것을 피할 가치가있을 정도로 느리지 않다"고 말했다. 나는 이것에 관한 두 개의 짧은 기사썼습니다 . 벤치 마크 측면에 대한 비판이 있습니다. 대부분 "실제로 더 많은 스택이 필요하므로 캐시를 날려 버릴 것입니다."-오류 코드를 사용하여 스택을 처리하는 방법 도 있습니다. 캐시를 날려 버려서 특히 좋은 주장으로 보지 않습니다.

명확하게하기 위해-논리적이지 않은 예외 사용은 지원하지 않습니다. 예를 들어, int.TryParse사용자의 데이터를 변환하는 데 전적으로 적합합니다. 기계가 생성 한 파일을 읽을 때 적절합니다. 여기서 실패는 "파일이 의도 한 형식이 아닙니다. 다른 문제가 무엇인지 알지 못하므로 처리하려고하지 않습니다." "

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


Chris Brumme을 구현 한 사람이 이에 대한 결정적인 대답이 있습니다. 그는 주제에 관한 훌륭한 블로그 기사를 썼습니다 (경고-매우 길다) (경고 2-아주 잘 쓰여졌습니다. 기술자라면 끝까지 읽은 다음 일과 후에 시간을 보내야합니다 :) )

행정상 요약 : 그들은 느리다. Win32 SEH 예외로 구현되므로 일부는 링 0 CPU 경계를 통과합니다! 분명히 현실 세계에서는 많은 다른 작업을 수행하므로 이상한 예외는 전혀 눈에 띄지 않지만 앱을 망치지 않고 프로그램 흐름에 사용하는 경우 예외가 발생하지 않습니다. 이것은 우리에게 장애를 일으키는 MS 마케팅 머신의 또 다른 예입니다. 나는 한 Microsoftie가 오버 헤드가 전혀 발생하지 않은 방식을 알려주는 것을 기억합니다.

Chris는 적절한 인용문을 제공합니다.

실제로 CLR은 엔진의 관리되지 않는 부분에서도 내부적으로 예외를 사용합니다. 그러나 예외와 관련하여 심각한 장기 성능 문제가 있으며 이는 결정에 반영되어야합니다.


나는 그들이 던져 질 때만 느리다고 말할 때 사람들이 무엇에 대해 이야기하고 있는지 전혀 모른다.

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

.NET 앱의 예외 워크 플로는 첫 번째 및 두 번째 기회 예외를 사용합니다. 모든 예외에 대해 예외를 잡아서 처리하더라도 예외 개체는 계속 생성되며 프레임 워크는 여전히 스택을 따라 처리기를 찾아야합니다. 더 오래 걸리는 코스를 잡아서 다시 던지면-첫 번째 예외가 발생하고 다시 잡아서 다시 던져서 또 다른 첫 번째 예외가 발생하여 핸들러를 찾지 못하게됩니다. 두 번째 기회 예외.

예외는 힙의 객체이기도하므로 많은 예외가 발생하면 성능 및 메모리 문제가 모두 발생합니다.

또한 ACE 팀이 작성한 "성능 테스트 Microsoft .NET 웹 응용 프로그램"의 사본에 따르면 :

"예외 처리는 비용이 많이 든다. 올바른 예외 처리기를 검색하기 위해 호출 스택을 통해 CLR이 되풀이되는 동안 관련 스레드의 실행이 일시 중단되고 발견되면 예외 처리기와 일부 최종 블록이 모두 실행될 기회를 가져야한다 정기적 인 처리를 수행하기 전에

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


내가 이해하는 논쟁은 예외를 던지는 것이 나쁘다는 것이 아닙니다. 대신, 일반적인 조건부 구문 대신 일반적인 응용 프로그램 논리를 제어하는 ​​첫 번째 방법으로 throw / catch 구문을 사용합니다.

일반적인 응용 프로그램 논리에서는 종종 같은 동작이 수천 / 백만 번 반복되는 루핑을 수행합니다. 이 경우 매우 간단한 프로파일 링 (Stopwatch 클래스 참조)을 사용하면 간단한 if 문 대신 예외를 throw하는 것이 실제로 느릴 수 있음을 알 수 있습니다.

사실 저는 Microsoft의 .NET 팀이 .NET 2.0의 TryXXXXX 메소드를 많은 기본 FCL 유형에 도입했다는 사실을 읽었습니다. 고객이 애플리케이션 성능이 너무 느리다고 불평했기 때문입니다.

많은 경우에 고객이 루프에서 값의 유형 변환을 시도하고 각 시도가 실패했기 때문입니다. 변환 예외가 발생하고 예외 처리기에 의해 포착 된 다음 예외를 삼켜 루프를 계속했습니다.

이러한 상황에서 가능한 성능 문제를 피하려면 특히이 상황에서 TryXXX 방법을 사용하는 것이 좋습니다.

내가 틀렸을 수도 있지만, 읽은 "벤치 마크"의 정확성에 대해 확신이없는 것 같습니다. 간단한 해결책 : 직접 사용해보십시오.


내 XMPP 서버는 일관되게 발생하지 않도록 (예 : 더 많은 데이터를 읽기 전에 소켓이 연결되어 있는지 확인) 시도하지 않고 피할 수있는 방법을 제공 한 후 주요 속도 향상 (미안, 실제 숫자 없음, 순전히 관찰)을 얻었습니다. (언급 된 TryX 방법). 약 50 명의 활성 (채팅) 가상 사용자 만있었습니다.


이 토론에 내 자신의 최근 경험을 추가하기 위해 위에서 언급 한 대부분의 내용에 따라 디버거를 실행하지 않아도 예외를 반복적으로 수행하면 예외가 매우 느려집니다. 방금 5 줄의 코드를 변경하여 작성중인 큰 프로그램의 성능을 60 % 향상 시켰습니다. 예외를 던지는 대신 리턴 코드 모델로 전환했습니다. 문제의 코드가 수천 번 실행되어 변경하기 전에 수천 건의 예외가 발생할 수 있습니다. 따라서 위의 진술에 동의합니다. "예상 된"상황에서 응용 프로그램 흐름을 제어하는 ​​방법이 아니라 중요한 것이 실제로 잘못 될 때 예외를 throw합니다.


리턴 코드와 비교하면 지옥만큼 느립니다. 그러나 이전 포스터에서 언급했듯이 정상적인 프로그램 작동을 포기하고 싶지 않으므로 문제가 발생했을 때만 성능이 저하되고 대부분의 경우 성능이 더 이상 중요하지 않습니다 (예외는로드 블록을 암시하므로).

오버 오류 코드를 사용하는 것이 가치가 있으며 이점은 광대 한 IMO입니다.


예외와 관련하여 성능 문제가 없었습니다. 예외를 많이 사용합니다. 가능하면 리턴 코드를 사용하지 않습니다. 그것들은 나쁜 습관이며, 제 생각에는 스파게티 코드 냄새가납니다.

나는 그것이 예외를 어떻게 사용하는지에 달려 있다고 생각한다 : 리턴 코드 (스택 캐치 및 다시 던지기의 각 메소드 호출)와 같은 예외를 사용하면 각 캐치 / 던지기마다 오버 헤드가 있기 때문에 느려질 것입니다.

그러나 스택의 맨 아래에 던져서 맨 위에 걸면 (전체 리턴 코드 체인을 하나의 스로우 / 캐치로 대체) 모든 비용이 많이 드는 작업이 한 번 수행됩니다.

하루가 끝나면 유효한 언어 기능입니다.

내 요점을 증명하기 위해

이 링크에서 코드를 실행하십시오 (답이 너무 큽니다).

내 컴퓨터의 결과 :

marco@sklivvz:~/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

타임 스탬프는 처음에 리턴 코드와 예외 사이에서 끝에 출력됩니다. 두 경우 모두 같은 시간이 걸립니다. 최적화를 사용하여 컴파일해야합니다.


그러나 모노는 .net 독립형 모드보다 10 배 더 빠르게 예외를 처리하고 .net 독립형 모드는 .net 디버거 모드보다 60 배 더 빠르게 예외를 처리합니다. (테스트 머신은 동일한 CPU 모델을 가짐)

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");

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

재귀 방식으로 흐름 제어 (예 : 로컬이 아닌 출구)에 예외를 사용하지 않는 한 차이가 있음을 알 수있을 것입니다.


Windows CLR에서 깊이 8 호출 체인의 경우 예외를 throw하면 반환 값을 확인하고 전파하는 것보다 750 배 느립니다. (아래 벤치 마크 참조)

이 예외에 대한 높은 비용은 Windows CLR이 Windows 구조적 예외 처리 라는 것과 통합되기 때문 입니다. 이를 통해 다른 런타임과 언어에서 예외를 올바르게 포착하고 처리 할 수 ​​있습니다. 그러나 매우 느립니다.

모든 플랫폼에서 Mono 런타임의 예외는 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));         }
        }
    }
}


}

예외 포착과 관련된 성능에 대한 간단한 참고 사항입니다.

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


When writing classes/functions for others to use it appears to be difficult to say when exceptions are appropriate. There are some useful parts of BCL that I had to ditch and go for pinvoke because they throw exceptions instead of returning errors. For some cases you can work around it but for others like System.Management and Performance Counters there are usages where you need to do loops in which exceptions are thrown by BCL frequently.

If you are writing a library and there's a remote possibility that your function may be used in a loop and there's a potential for large amount of iterations, use the Try.. pattern or some other way to expose the errors beside exceptions. And even then, it's hard to say how much your function will get called if it's being used by many processes in shared environment.

In my own code, exceptions are only thrown when things are so exceptional that its necessary to go look at the stack trace and see what went wrong and then fix it. So I pretty much have re-written parts of BCL to use error handling based on Try.. pattern instead of exceptions.

참고URL : https://stackoverflow.com/questions/161942/how-slow-are-net-exceptions

반응형