programing

C #에서 try / catch의 실제 오버 헤드는 무엇입니까?

nasanasas 2020. 9. 3. 19:32
반응형

C #에서 try / catch의 실제 오버 헤드는 무엇입니까?


그래서 저는 try / catch가 약간의 오버 헤드를 추가하므로 프로세스 흐름을 제어하는 ​​좋은 방법이 아니라는 것을 알고 있습니다. 그러나이 오버 헤드는 어디에서 왔으며 실제 영향은 무엇입니까?


나는 언어 구현에 대한 전문가는 아니지만 (그러니 이것을 염두에 두십시오), 가장 큰 비용 중 하나는 스택을 풀고 스택 추적을 위해 저장하는 것이라고 생각합니다. 예외가 던져 질 때만 이런 일이 발생한다고 생각합니다 (하지만 모르겠습니다). 그렇다면 예외가 발생할 때마다 상당한 크기의 숨겨진 비용이 될 것입니다 ... 그래서 한곳에서 뛰어 내리는 것과는 다릅니다. 다른 코드에서는 많은 일이 진행되고 있습니다.

예외적 인 동작에 대해 예외를 사용하는 한 문제가되지 않는다고 생각합니다 (프로그램을 통한 일반적인 예상 경로가 아님).


여기서 확인해야 할 세 가지 사항 :

  • 첫째, 실제로 코드에서 try-catch 블록을 사용하는 경우 성능 저하가 거의 또는 전혀 없습니다. 응용 프로그램에 포함하지 않으려 고 할 때 고려해서는 안됩니다. 성능 저하는 예외가 발생할 때만 작용합니다.

  • 다른 사람들이 언급 한 스택 해제 작업 등에 추가로 예외가 발생하면 스택 추적과 같은 예외 클래스의 구성원을 채우기 위해 런타임 / 반사 관련 작업이 많이 발생한다는 사실을 알아야합니다. 객체 및 다양한 유형 멤버 등

  • 나는 이것이 throw;예외를 다시 던질 때 일반적인 조언 이 예외를 다시 던지거나 새로운 것을 생성하는 것보다 단순한 이유 중 하나라고 생각합니다. 던지는 것은 모두 보존됩니다.


예외가 발생하지 않을 때 try / catch / finally를 사용하는 오버 헤드 또는 프로세스 흐름을 제어하기 위해 예외를 사용하는 오버 헤드에 대해 질문하고 있습니까? 후자는 다이너마이트 막대를 사용하여 유아의 생일 촛불을 밝히는 것과 다소 유사하며 관련 오버 헤드는 다음 영역에 속합니다.

  • 일반적으로 캐시에 있지 않은 상주 데이터에 액세스하는 예외로 인해 추가 캐시 누락이 발생할 수 있습니다.
  • 일반적으로 애플리케이션의 작업 세트에없는 비상주 코드 및 데이터에 액세스하는 예외로 인해 추가 페이지 오류가 발생할 수 있습니다.

    • 예를 들어 예외를 throw하려면 CLR이 현재 IP 및 모든 프레임의 반환 IP를 기반으로 finally 및 catch 블록의 위치를 ​​찾아야하며 예외가 처리 될 때까지 필터 블록을 추가해야합니다.
    • 메타 데이터 읽기 등 진단 목적으로 프레임을 생성하기위한 추가 건설 비용 및 이름 확인
    • 위의 두 항목 모두 일반적으로 "콜드"코드와 데이터에 액세스하므로 메모리 부족이 발생하면 하드 페이지 오류가 발생할 수 있습니다.

      • CLR은 지역성을 개선하기 위해 자주 사용되는 데이터와는 거리가 멀어 자주 사용되는 코드와 데이터를 넣으려고하므로 추위를 더워지게하므로 불리하게 작용합니다.
      • 하드 페이지 폴트의 비용은 다른 모든 것을 축소시킵니다.
  • 일반적인 catch 상황은 종종 깊기 때문에 위의 효과가 확대되는 경향이 있습니다 (페이지 폴트 가능성 증가).

비용의 실제 영향은 당시 코드에서 진행중인 다른 작업에 따라 크게 달라질 수 있습니다. Jon Skeet는 유용한 링크와 함께 여기에 좋은 요약을 제공 합니다. 나는 예외로 인해 성능이 크게 저하되는 지점에 도달하면 성능뿐만 아니라 예외를 사용하는 데 문제가 있다는 그의 진술에 동의하는 경향이 있습니다.


내 경험상 가장 큰 오버 헤드는 실제로 예외를 던지고 처리하는 것입니다. 나는 누군가가 어떤 객체를 편집 할 권한이 있는지 확인하기 위해 다음과 유사한 코드를 사용하는 프로젝트에서 일한 적이 있습니다. 이 HasRight () 메서드는 프레젠테이션 레이어의 모든 곳에서 사용되었으며 종종 수백 개의 개체에 대해 호출되었습니다.

bool HasRight(string rightName, DomainObject obj) {
  try {
    CheckRight(rightName, obj);
    return true;
  }
  catch (Exception ex) {
    return false;
  }
}

void CheckRight(string rightName, DomainObject obj) {
  if (!_user.Rights.Contains(rightName))
    throw new Exception();
}

테스트 데이터베이스가 테스트 데이터로 가득 차면 새 양식을 여는 동안 매우 눈에 띄는 속도 저하가 발생합니다.

그래서 나는 그것을 다음과 같이 리팩토링했는데, 나중에 빠른 측정에 따르면 약 2 배 더 빠릅니다.

bool HasRight(string rightName, DomainObject obj) {
  return _user.Rights.Contains(rightName);
}

void CheckRight(string rightName, DomainObject obj) {
  if (!HasRight(rightName, obj))
    throw new Exception();
}

즉, 정상적인 프로세스 흐름에서 예외를 사용하는 것은 예외없이 유사한 프로세스 흐름을 사용하는 것보다 약 2 배 더 느립니다.


자주 호출되는 메서드 내부에 있는지는 말할 것도없이 애플리케이션의 전반적인 동작에 영향을 미칠 수 있습니다.
예를 들어, Int32.Parse를 사용하면 다른 방법으로 쉽게 잡을 수있는 예외가 발생하므로 대부분의 경우 나쁜 습관으로 간주합니다.

따라서 여기에 쓰여진 모든 것을 결론
짓기 위해 : 1) try..catch 블록을 사용하여 예상치 못한 오류를 포착합니다. 성능 저하가 거의 없습니다.
2) 피할 수 있다면 예외 오류에 예외를 사용하지 마십시오.


그 당시 많은 사람들이 이것에 대해 물어 보았 기 때문에 얼마 전에 이것에 대한 기사를 썼습니다. http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx 에서이 코드와 테스트 코드를 찾을 수 있습니다 .

결론은 try / catch 블록에 약간의 오버 헤드가 있지만 무시해야 할 정도로 작다는 것입니다. 그러나 수백만 번 실행되는 루프에서 try / catch 블록을 실행하는 경우 가능하면 블록을 루프 외부로 이동하는 것을 고려할 수 있습니다.

try / catch 블록의 핵심 성능 문제는 실제로 예외를 포착 할 때입니다. 이로 인해 응용 프로그램이 눈에 띄게 지연 될 수 있습니다. 물론 문제가 발생하면 대부분의 개발자 (및 많은 사용자)는 일시 중지를 곧 일어날 예외로 인식합니다! 여기서 핵심은 정상적인 작업에 예외 처리를 사용하지 않는 것입니다. 이름에서 알 수 있듯이, 그들은 예외적이며 던져지지 않도록 할 수있는 모든 것을해야합니다. 올바르게 작동하는 프로그램의 예상 흐름의 일부로 사용해서는 안됩니다.


일반적으로 받아 들여지는 이론과는 달리 try/ catch는 성능에 상당한 영향을 미칠 수 있으며 예외가 발생하는지 여부입니다!

  1. 일부 자동 최적화를 비활성화하고 (설계 상) 일부 경우 디버깅 지원 에서 예상 할 수있는 디버깅 코드를 삽입 합니다. 이 점에서 항상 저와 동의하지 않는 사람들이있을 것이지만, 언어는 그것을 필요로하고 분해는 그것을 보여줍니다. 그래서 그 사람들은 사전 정의에 의해 망상 적 입니다.
  2. 유지 관리에 부정적인 영향을 미칠 수 있습니다. 이것은 실제로 여기에서 가장 중요한 문제이지만 (거의 전적으로 그것에 초점을 맞춘) 마지막 답변이 삭제되었으므로 더 중요한 문제가 아닌 덜 중요한 문제 (마이크로 최적화)에 초점을 맞추려고 노력할 것입니다 ( 거시적 최적화).

전자는 지난 몇 년 동안 마이크로 소프트의 MVP로 블로그 게시물의 몇 가지에 덮여, 그리고 당신이 쉽게 찾을 수있는 신뢰 아직 StackOverflow의이 걱정되어 훨씬 에 대한 내용 나는대로 그들 중 일부에 대한 링크를 제공 할 수 있습니다 있도록 필러 증거 :

  • try/ catch/finally ( 및 파트 2 ) 의 성능 영향 , Peter Ritchie가try/catch/finally비활성화하는 최적화를 탐색합니다 (표준에서 인용하여 더 자세히 설명하겠습니다).
  • 성능 프로파일 링 ParseTryParseConvertTo 이안 허프으로는 "예외 처리가 매우 느립니다"과 내공으로이 점을 보여줍니다 있음을 노골적으로 주장Int.Parse하고Int.TryParse주장하는 사람이 서로 ...에 대하여 그TryParse용도가try/catch무대 뒤에서,이 되거한다고!

/ 사용 여부와 상관없이 분해 된 코드의 차이점을 보여주는 이 답변 도 있습니다 .trycatch

코드 생성에서 노골적으로 관찰 할 수있는 오버 헤드 가 있다는 것이 너무 분명해 보이며 , 그 오버 헤드는 Microsoft가 중요하게 여기는 사람들에게도 인정되는 것 같습니다! 그러나 나는 인터넷을 반복하고 있습니다 ...

예, 한 줄의 간단한 코드에 대해 수십 개의 추가 MSIL 명령이 있으며 비활성화 된 최적화도 다루지 않으므로 기술적으로는 마이크로 최적화입니다.


프로그래머의 생산성 (매크로 최적화)에 초점을 맞춰 삭제 된 답변을 몇 년 전에 게시했습니다.

여기저기서 CPU 시간의 몇 나노초를 절약하지 않아도 인간이 수동으로 최적화하는 데 많은 시간이 누적 된 것을 보충 할 수 있기 때문에 이는 불행한 일입니다. 상사가 더 많은 비용을 지불하는 것은 무엇입니까? 한 시간 중 한 시간입니까, 아니면 컴퓨터가 실행중인 한 시간입니까? 어느 시점에서 우리는 플러그를 뽑고 더 빠른 컴퓨터를 구입할 때임을 인정 합니까?

분명히 우리는 코드뿐만 아니라 우선 순위를 최적화 해야합니다 ! 마지막 답변에서 두 코드 스 니펫의 차이점을 그렸습니다.

try/ 사용 catch:

int x;
try {
    x = int.Parse("1234");
}
catch {
    return;
}
// some more code here...

try/ 사용하지 않음 catch:

int x;
if (int.TryParse("1234", out x) == false) {
    return;
}
// some more code here

프로파일 링 / 최적화 (위에서 다루었 음)가 아니라면 시간을 낭비 할 가능성이 더 큰 유지 보수 개발자의 관점에서 고려하십시오. try/ catch문제 가 아니었다면 필요하지 않을 수도 있습니다. 소스 코드 ... 그중 하나는 4 줄의 상용구 쓰레기를 추가로 가지고 있습니다!

점점 더 많은 필드가 클래스에 도입됨에 따라이 모든 보일러 플레이트 가비지 (소스 및 디스 어셈블 된 코드 모두)가 합리적인 수준을 훨씬 넘어 누적됩니다. 필드 당 4 개의 추가 줄이 있고 항상 같은 줄입니다. 반복하지 않도록 배웠습니까? 나는 우리가 숨길 수있는 가정 try/ catch우리뿐만 아니라 단지 예외 (즉, 사용하지 있습니다 ... 일부 홈 양조 추상화 뒤,하지만 Int.TryParse).

이것은 복잡한 예가 아닙니다. try/ 에서 새 클래스를 인스턴스화하려는 시도를 보았습니다 catch. 생성자 내부의 모든 코드가 컴파일러에 의해 자동으로 적용되는 특정 최적화에서 실격 될 수 있음을 고려하십시오. 컴파일러가 지시 된대로 정확히 수행하는 것과 달리 컴파일러가 느리다는 이론을 일으키는 더 좋은 방법은 무엇입니까 ?

생성자에 의해 예외가 발생하고 그 결과로 일부 버그가 트리거된다고 가정하면 열악한 유지 관리 개발자는이를 추적해야합니다. 즉,의 스파게티 코드와는 달리으로, 같은 쉬운 일이되지 않을 수도 있습니다 고토 악몽, try/ catch에 혼잡이 발생할 수 있습니다 세 가지 차원 이 다른 클래스와 메소드를 같은 방법의 단지 다른없는 부분으로 스택을 이동하지만, 수 있기 때문에, , 모두는 유지 보수 개발자에 의해 관찰 될 것이다 어려운 방법 ! 그러나 우리는 "goto는 위험하다"라고 들었습니다, heh!

마지막으로, try/ 최적화를 비활성화하도록 설계된catch 이점이 있습니다 ! 당신이 원한다면 그것은 디버깅 보조 도구입니다 ! 그것이 그것이 설계된 이유이고 그것이 사용되어야하는 것입니다 ...

그것도 긍정적 인 점이라고 생각합니다. 멀티 스레드 애플리케이션을위한 안전하고 건전한 메시지 전달 알고리즘을 손상시킬 수있는 최적화를 비활성화하고 가능한 경쟁 조건을 포착하는 데 사용할 수 있습니다.;) 이것이 try / catch를 사용하는 유일한 시나리오입니다. 그것조차 대안이 있습니다.


무엇 최적화는 수행 try, catchfinally비활성화?

AKA

어떻게하다 try, catchfinally보조 디버깅 유용?

쓰기 장벽입니다. 이것은 표준에서 비롯됩니다.

12.3.3.13 Try-catch 문

다음 형식의 문장 stmt 의 경우 :

try try-block
catch ( ... ) catch-block-1
... 
catch ( ... ) catch-block-n
  • 의 명확한 할당 상태 V 의 시작 부분에 시도-블록 의 명확한 할당 상태와 동일한 V 의 시작 부분에 STMT .
  • catch-block-i 시작시 v 의 명확한 할당 상태 (모든 i에 대해 )는 stmt 시작에서 v 의 명확한 할당 상태와 동일합니다 .
  • The definite assignment state of v at the end-point of stmt is definitely assigned if (and only if) v is definitely assigned at the end-point of try-block and every catch-block-i (for every i from 1 to n).

In other words, at the beginning of each try statement:

  • all assignments made to visible objects prior to entering the try statement must be complete, which requires a thread lock for a start, making it useful for debugging race conditions!
  • the compiler isn't allowed to:
    • eliminate unused variable assignments which have definitely been assigned to before the try statement
    • reorganise or coalesce any of it's inner-assignments (i.e. see my first link, if you haven't already done so).
    • hoist assignments over this barrier, to delay assignment to a variable which it knows won't be used until later (if at all) or to pre-emptively move later assignments forward to make other optimisations possible...

A similar story holds for each catch statement; suppose within your try statement (or a constructor or function it invokes, etc) you assign to that otherwise pointless variable (let's say, garbage=42;), the compiler can't eliminate that statement, no matter how irrelevant it is to the observable behaviour of the program. The assignment needs to have completed before the catch block is entered.

For what it's worth, finally tells a similarly degrading story:

12.3.3.14 Try-finally statements

For a try statement stmt of the form:

try try-block
finally finally-block

• The definite assignment state of v at the beginning of try-block is the same as the definite assignment state of v at the beginning of stmt.
• The definite assignment state of v at the beginning of finally-block is the same as the definite assignment state of v at the beginning of stmt.
• The definite assignment state of v at the end-point of stmt is definitely assigned if (and only if) either: o v is definitely assigned at the end-point of try-block o v is definitely assigned at the end-point of finally-block If a control flow transfer (such as a goto statement) is made that begins within try-block, and ends outside of try-block, then v is also considered definitely assigned on that control flow transfer if v is definitely assigned at the end-point of finally-block. (This is not an only if—if v is definitely assigned for another reason on this control flow transfer, then it is still considered definitely assigned.)

12.3.3.15 Try-catch-finally statements

Definite assignment analysis for a try-catch-finally statement of the form:

try try-block
catch ( ... ) catch-block-1
... 
catch ( ... ) catch-block-n
finally finally-block

is done as if the statement were a try-finally statement enclosing a try-catch statement:

try { 
    try   
    try-block
    catch ( ... ) catch-block-1
    ...   
    catch ( ... ) catch-block-n
} 
finally finally-block

I made a blog entry about this subject last year. Check it out. Bottom line is that there is almost no cost for a try block if no exception occurs - and on my laptop, an exception was about 36μs. That might be less than you expected, but keep in mind that those results where on a shallow stack. Also, first exceptions are really slow.


It is vastly easier to write, debug, and maintain code that is free of compiler error messages, code-analysis warning messages, and routine accepted exceptions (particularly exceptions that are thrown in one place and accepted in another). Because it is easier, the code will on average be better written and less buggy.

To me, that programmer and quality overhead is the primary argument against using try-catch for process flow.

The computer overhead of exceptions is insignificant in comparison, and usually tiny in terms of the application's ability to meet real-world performance requirements.


I really like Hafthor's blog post, and to add my two cents to this discussion, I'd like to say that, it's always been easy for me to have the DATA LAYER throw only one type of exception (DataAccessException). This way my BUSINESS LAYER knows what exception to expect and catches it. Then depending on further business rules (i.e. if my business object participates in the workflow etc), I may throw a new exception (BusinessObjectException) or proceed without re/throwing.

I'd say don't hesitate to use try..catch whenever it is necessary and use it wisely!

For example, this method participates in a workflow...

Comments?

public bool DeleteGallery(int id)
{
    try
    {
        using (var transaction = new DbTransactionManager())
        {
            try
            {
                transaction.BeginTransaction();

                _galleryRepository.DeleteGallery(id, transaction);
                _galleryRepository.DeletePictures(id, transaction);

                FileManager.DeleteAll(id);

                transaction.Commit();
            }
            catch (DataAccessException ex)
            {
                Logger.Log(ex);
                transaction.Rollback();                        
                throw new BusinessObjectException("Cannot delete gallery. Ensure business rules and try again.", ex);
            }
        }
    }
    catch (DbTransactionException ex)
    {
        Logger.Log(ex);
        throw new BusinessObjectException("Cannot delete gallery.", ex);
    }
    return true;
}

We can read in Programming Languages Pragmatics by Michael L. Scott that the nowadays compilers do not add any overhead in common case, this means, when no exceptions occurs. So every work is made in compile time. But when an exception is thrown in run-time, compiler needs to perform a binary search to find the correct exception and this will happen for every new throw that you made.

But exceptions are exceptions and this cost is perfectly acceptable. If you try to do Exception Handling without exceptions and use return error codes instead, probably you will need a if statement for every subroutine and this will incur in a really real time overhead. You know a if statement is converted to a few assembly instructions, that will performed every time you enter in your sub-routines.

Sorry about my English, hope that it helps you. This information is based on cited book, for more information refer to Chapter 8.5 Exception Handling.


Let us analyse one of the biggest possible costs of a try/catch block when used where it shouldn't need to be used:

int x;
try {
    x = int.Parse("1234");
}
catch {
    return;
}
// some more code here...

And here's the one without try/catch:

int x;
if (int.TryParse("1234", out x) == false) {
    return;
}
// some more code here

Not counting the insignificant white-space, one might notice that these two equivelant pieces of code are almost exactly the same length in bytes. The latter contains 4 bytes less indentation. Is that a bad thing?

To add insult to injury, a student decides to loop while the input can be parsed as an int. The solution without try/catch might be something like:

while (int.TryParse(...))
{
    ...
}

But how does this look when using try/catch?

try {
    for (;;)
    {
        x = int.Parse(...);
        ...
    }
}
catch
{
    ...
}

Try/catch blocks are magical ways of wasting indentation, and we still don't even know the reason it failed! Imagine how the person doing debugging feels, when code continues to execute past a serious logical flaw, rather than halting with a nice obvious exception error. Try/catch blocks are a lazy man's data validation/sanitation.

One of the smaller costs is that try/catch blocks do indeed disable certain optimizations: http://msmvps.com/blogs/peterritchie/archive/2007/06/22/performance-implications-of-try-catch-finally.aspx. I guess that's a positive point too. It can be used to disable optimizations that might otherwise cripple safe, sane message passing algorithms for multithreaded applications, and to catch possible race conditions ;) That's about the only scenario I can think of to use try/catch. Even that has alternatives.

참고URL : https://stackoverflow.com/questions/52312/what-is-the-real-overhead-of-try-catch-in-c

반응형