C ++의 예외가 정말 느립니까?


98

저는 C ++의 체계적인 오류 처리를 보고있었습니다. Andrei Alexandrescu 는 C ++의 예외가 매우 느리다고 주장합니다.

C ++ 98에서도 여전히 그렇습니까?


42
"C ++ 98 예외"가 "C ++ 03 예외"또는 "C ++ 11 예외"보다 빠르거나 느린 지 묻는 것은 의미가 없습니다. 이들의 성능은 컴파일러가 프로그램에서이를 구현하는 방법에 따라 달라지며 C ++ 표준은 구현 방법에 대해 아무 것도 언급하지 않습니다. 유일한 요구 사항은 행동이 표준 ( "as-if"규칙)을 따라야한다는 것입니다.
In silico

관련 (실제로 중복되지는 않음) 질문 : stackoverflow.com/questions/691168/…
Philipp dec.

2
네, 아주 천천히,하지만 그들은 지점으로 정상적인 작업에 던져 또는 사용할 수 없습니다
BЈовић


BЈовић가 말한 것을 명확히하기 위해, 예외를 사용하는 것은 두려워 할 것이 아닙니다. (잠재적으로) 시간이 많이 걸리는 작업이 발생하는 예외가 발생하는 경우입니다. 또한 C ++ 89에 대해 구체적으로 알고 싶은 이유가 궁금합니다. 최신 버전은 C ++ 11이고 예외를 실행하는 데 걸리는 시간은 구현이 정의되어 있으므로 '잠재적으로'시간이 많이 걸립니다. .
thecoshman

답변:


162

오늘날 예외에 사용되는 주요 모델 (Itanium ABI, VC ++ 64 비트)은 비용이 0 인 모델 예외입니다.

아이디어는 가드를 설정하고 모든 곳에서 예외가 있는지 명시 적으로 확인하여 시간을 낭비하는 대신 컴파일러가 예외 (프로그램 카운터)를 throw 할 수있는 지점을 핸들러 목록에 매핑하는 부가 테이블을 생성한다는 것입니다. 예외가 발생하면이 목록을 참조하여 올바른 핸들러 (있는 경우)를 선택하고 스택이 풀립니다.

일반적인 if (error)전략과 비교 :

  • 이름에서 알 수 있듯이 Zero-Cost 모델은 예외가 발생하지 않으면 무료입니다.
  • if예외 발생시 약 10 배 / 20 배 비용

그러나 비용은 측정하기가 간단하지 않습니다.

  • 사이드 테이블은 일반적으로 메모리로부터 페치하는 것은 시간이 오래 걸리는 따라서, 및
  • 올바른 핸들러를 결정하는 데는 RTTI가 포함됩니다. 페치 할 많은 RTTI 설명자, 메모리 주변에 흩어져 있고 실행할 복잡한 작업 (기본적으로 dynamic_cast각 핸들러에 대한 테스트)

따라서 대부분 캐시 미스이므로 순수한 CPU 코드에 비해 사소하지 않습니다.

참고 : 자세한 내용은 TR18015 보고서, 5.4 장 예외 처리 (pdf)를 참조하십시오.

예, 예외는 예외적 인 경로 에서 느리지 만 if일반적 으로 명시 적 검사 ( 전략) 보다 빠릅니다 .

참고 : Andrei Alexandrescu는이 "빠른"질문을하는 것 같습니다. 저는 개인적으로 상황이 양방향으로 바뀌는 것을 보았습니다. 일부 프로그램은 예외가있는 경우 더 빠르며 다른 프로그램은 브랜치에서 더 빠르기 때문에 실제로 특정 조건에서 최적화 가능성이 손실되는 것 같습니다.


그게 그렇게 중요한 건가 ?

나는 그렇지 않다고 주장 할 것이다. 프로그램은 성능이 아닌 가독성 을 염두에두고 작성해야합니다 (적어도 첫 번째 기준은 아닙니다). 예외는 호출자가 그 자리에서 실패를 처리 할 수 ​​없거나 처리하기를 원하지 않을 것으로 예상하고 스택 위로 전달하는 경우에 사용됩니다. 보너스 : C ++ 11에서는 표준 라이브러리를 사용하여 스레드간에 예외를 마샬링 할 수 있습니다.

이것은 미묘하지만 map::find던지지 않아야한다고 주장 하지만 null이기 때문에 역 참조 시도가 실패하면 던지는 map::finda checked_ptr를 반환 해도 괜찮 습니다. 후자의 경우 Alexandrescu가 소개 한 클래스의 경우와 같이 호출자가 선택합니다. 명시 적 검사와 예외 의존 사이. 더 많은 책임을주지 않고 발신자에게 권한을 부여하는 것은 일반적으로 좋은 디자인의 신호입니다.


3
+1 네 가지만 추가하겠습니다. (0) C ++ 11에 추가 된 rethrowing 지원에 대해; (1) C ++ 효율성에 대한위원회의 보고서 참조; (2) 정확성에 대한 몇 가지 언급 (가독성을 능가하는 것) 그리고 (3) 성능에 대해, 예외를 사용하지 않는 경우에 대한 측정에 대한 언급 (모두 상대적 임)
건배 및 hth. -Alf

2
@ Cheersandhth.-Alf : (0), (1) 및 (3) 완료 : 감사합니다. 정확성과 관련하여 (2) 가독성을 능가하지만 다른 오류 처리 전략보다 더 정확한 코드로 이어지는 예외에 대해서는 확신하지 못합니다 (실행 예외의 많은 보이지 않는 경로를 잊어 버리기 쉽습니다).
Matthieu M.

2
설명은 로컬에서 정확할 수 있지만 예외의 존재는 컴파일러가 수행 할 수있는 가정 및 최적화에 전역 적 영향을 미친다는 점에 주목할 가치가 있습니다. 이러한 의미는 컴파일러가 항상 작은 프로그램을 통해 볼 수 있기 때문에 "사소한 반례가 없음"이라는 문제를 안고 있습니다. 예외를 포함하거나 포함하지 않는 현실적이고 큰 코드 기반에 대한 프로파일 링은 좋은 생각 일 수 있습니다.
Kerrek SB

4
> 이름에서 알 수 있듯이 Zero-Cost 모델은 예외가 발생하지 않으면 무료입니다. 이것은 실제로 가장 세부적인 수준까지 사실이 아닙니다. 더 많은 코드를 생성하면 작지만 미묘하더라도 항상 성능에 영향을 미칩니다. OS가 실행 파일을로드하는 데 조금 더 오래 걸리거나 더 많은 i- 캐시 미스가 발생할 수 있습니다. 또한 스택 해제 코드는 어떻습니까? 또한 합리적 사고로 이해하려고하는 대신 효과를 측정하기 위해 할 수있는 실험은 어떻습니까?
jheriko 2015 년

2
@jheriko : 실제로 대부분의 질문을 이미 해결했다고 생각합니다. 로드 시간은 영향을받지 않아야하며 (콜드 코드는로드되지 않아야 함) i- 캐시는 영향을받지 않아야합니다 (콜드 코드는 i- 캐시로 들어가서는 안 됨) ... 따라서 누락 된 질문 하나를 해결합니다. "측정 방법"=> 발생하는 예외를에 대한 호출로 대체 abort하면 바이너리 크기 풋 프린트를 측정하고로드 시간 / i- 캐시가 유사하게 작동하는지 확인할 수 있습니다. 당연히, 어떤 것도 치지 않는 것이 좋습니다 abort...
Matthieu M.

60

질문이 게시되었을 때 나는 택시를 기다리고 의사에게가는 중이었기 때문에 짧은 코멘트를 할 시간이있었습니다. 그러나 이제 댓글을 달고 찬성 및 반대 투표를 했으므로 내 답변을 추가하는 것이 좋습니다. Matthieu의 대답이 이미 꽤 좋다고 해도 .


C ++에서 예외가 다른 언어에 비해 특히 느립니까?

다시 주장

"저는 C ++의 체계적인 오류 처리를 보고있었습니다. Andrei Alexandrescu 는 C ++의 예외가 매우 느리다고 주장합니다."

그게 말 그대로 안드레이가 주장하는 바라면, 완전히 틀린 것은 아니지만 한 번 그는 매우 오해의 소지가 있습니다. 발생 / 발생 예외 의 경우 프로그래밍 언어에 관계없이 언어의 다른 기본 작업에 비해 항상 느립니다. . 주장 된 주장에서 알 수 있듯이 C ++뿐만 아니라 다른 언어보다 C ++에서도 그렇습니다.

일반적으로 언어에 관계없이 복잡한 데이터 구조를 처리하는 루틴 호출로 변환되기 때문에 나머지보다 훨씬 느린 두 가지 기본 언어 기능은 다음과 같습니다.

  • 예외 발생 및

  • 동적 메모리 할당.

다행히 C ++에서는 시간이 중요한 코드에서 두 가지를 모두 피할 수 있습니다.

불행히도 공짜 점심 같은 건 없다 C ++의 기본 효율성이 거의 비슷하더라도 . :-) 예외 발생 및 동적 메모리 할당을 피하여 얻은 효율성을 위해 일반적으로 C ++를 "더 나은 C"로 사용하여 더 낮은 수준의 추상화에서 코딩함으로써 달성됩니다. 그리고 낮은 추상화는 더 큰“복잡성”을 의미합니다.

복잡성이 클수록 유지 관리에 더 많은 시간이 소요되고 코드 재사용으로 인한 혜택이 거의 없거나 전혀 없습니다. 이는 추정하거나 측정하기 어렵더라도 실제 금전적 비용입니다. 즉, C ++를 사용하면 원하는 경우 실행 효율성을 위해 프로그래머 효율성을 바꿀 수 있습니다. 그렇게 할 것인지 여부는 대부분 엔지니어링 및 직감적 인 결정입니다. 실제로 비용이 아닌 이익 만 쉽게 추정하고 측정 할 수 있기 때문입니다.


C ++ 예외 발생 성능에 대한 객관적인 측정이 있습니까?

예, 국제 C ++ 표준화위원회는 C ++ 성능에 대한 기술 보고서 ​​TR18015를 발표했습니다 .


예외가 "느리다"는 것은 무엇을 의미 합니까?

주로 이는 핸들러 검색으로 인해 할당에 throw비해 Very Long Time ™이 소요될 수 있음을 의미 int합니다.

TR18015가 섹션 5.4“예외”에서 논의했듯이 두 가지 주요 예외 처리 구현 전략이 있습니다.

  • try-block이 예외 포착을 동적으로 설정하여 예외가 발생할 때 처리기의 동적 체인을 검색 하는 접근 방식

  • 컴파일러가 throw 된 예외에 대한 처리기를 결정하는 데 사용되는 정적 조회 테이블을 생성하는 방법입니다.

첫 번째 매우 유연하고 일반적인 접근 방식은 32 비트 Windows에서 거의 강제되는 반면 64 비트 및 * nix-land에서는 두 번째로 훨씬 더 효율적인 접근 방식이 일반적으로 사용됩니다.

또한이 보고서에서 설명하는 것처럼 각 접근 방식에 대해 예외 처리가 효율성에 영향을 미치는 세 가지 주요 영역이 있습니다.

  • try-블록,

  • 정규 기능 (최적화 기회) 및

  • throw-표현.

주로 동적 핸들러 접근 방식 (32 비트 Windows) 예외 처리에 영향이있다 try(이것은 'Windows에서 강제하기 때문에 대부분의 언어에 상관없이, 블록을 예외 처리 구조적 정적 테이블 접근 방식에 대한 대략 제로 비용을 가지고있는 동안, 계획을) try- 블록. 이것에 대해 논의하는 것은 SO 대답에 실용적인 것보다 훨씬 더 많은 공간과 연구가 필요할 것입니다. 따라서 자세한 내용은 보고서를 참조하십시오.

불행히도 2006 년의 보고서는 이미 2012 년 말 기준으로 작성되었으며, 내가 아는 한 더 새로운 것과 비교할만한 것은 없습니다.

또 다른 중요한 관점은 예외 사용이 성능에 미치는 영향이 지원 언어 기능의 분리 된 효율성과 매우 다르다는 것입니다.

"예외 처리를 고려할 때 오류를 처리하는 다른 방법과 대조되어야합니다."

예를 들면 :

  • 다양한 프로그래밍 스타일 (정확성)로 인한 유지 관리 비용

  • 중복 호출 사이트 if오류 확인 및 중앙 집중식try

  • 캐싱 문제 (예 : 짧은 코드가 캐시에 들어갈 수 있음)

보고서에는 고려해야 할 측면 목록이 다르지만 어쨌든 실행 효율성에 대한 확실한 사실을 얻는 유일한 실질적인 방법은 예외를 사용하지 않고 예외를 사용하여 동일한 프로그램을 개발 시간에 대한 결정된 한도 내에서 개발자와 함께 구현하는 것입니다. 각 방법에 익숙한 다음 MEASURE .


예외의 오버 헤드를 피하는 좋은 방법은 무엇입니까?

정확성 은 거의 항상 효율성보다 우선합니다.

예외없이 다음과 같은 일이 쉽게 발생할 수 있습니다.

  1. 일부 코드 P는 리소스를 얻거나 일부 정보를 계산하기위한 것입니다.

  2. 호출 코드 C는 성공 / 실패를 확인해야했지만 그렇지 않습니다.

  3. 존재하지 않는 리소스 또는 유효하지 않은 정보가 C 다음에 오는 코드에 사용되어 일반적인 혼란을 야기합니다.

주된 문제는 (2) 지점입니다. 일반적인 리턴 코드 체계에서는 호출 코드 C가 강제로 확인되지 않습니다.

이러한 검사를 수행하는 두 가지 주요 접근 방식이 있습니다.

  • P는 실패 할 때 직접 예외를 던집니다.

  • 여기서 P는 기본 값을 사용 하기 전에 C가 검사해야하는 객체를 반환합니다 (그렇지 않으면 예외 또는 종료).

두 번째 접근 방식은 AFAIK로 Barton과 Nackman이 책 * Scientific and Engineering C ++ : An Introduction with Advanced Techniques and Examples 에서 Fallow"가능한"함수 결과를 요구하는 클래스를 소개했습니다 . 유사한 클래스 optional가 이제 Boost 라이브러리에서 제공됩니다. 그리고 non-POD 결과의 경우 as value carrier를 Optional사용하여 클래스를 직접 쉽게 구현할 수 있습니다 std::vector.

첫 번째 접근 방식에서는 호출 코드 C가 예외 처리 기술을 사용할 수밖에 없습니다. 그러나 두 번째 접근 방식을 사용하면 호출 코드 C가 자체적 if으로 기반 검사 를 수행할지 또는 일반적인 예외 처리 를 수행할지 결정할 수 있습니다 . 따라서 두 번째 접근 방식은 프로그래머와 실행 시간 효율성의 절충을 지원합니다.


예외 성능에 대한 다양한 C ++ 표준의 영향은 무엇입니까?

"나는 이것이 C ++ 98에서도 여전히 사실인지 알고 싶다"

C ++ 98은 최초의 C ++ 표준이었습니다. 예외를 위해 예외 클래스의 표준 계층 구조를 도입했습니다 (불행하게도 다소 불완전합니다). 성능에 대한 주된 영향은 예외 사양 (C ++ 11에서 제거됨) 의 가능성 이었지만 주 Windows C ++ 컴파일러에서 완전히 구현되지는 않았습니다. Visual C ++ : Visual C ++는 C ++ 98 예외 사양 구문을 허용하지만 무시합니다. 예외 사양.

C ++ 03은 C ++ 98의 기술적 정답 일뿐입니다. C ++ 03의 유일한 새로운 기능은 값 초기화 였습니다. 예외와는 아무 관련이 없습니다.

C ++ 11 표준 일반 예외 사양은 제거되고 noexcept키워드 로 대체되었습니다 .

C ++ 11 표준은 또한 예외를 저장하고 다시 던지는 지원을 추가했습니다. 이는 C 언어 콜백에서 C ++ 예외를 전파하는 데 유용합니다. 이 지원은 현재 예외를 저장할 수있는 방법을 효과적으로 제한합니다. 그러나 내가 아는 한, 새로운 코드에서 예외 처리가 C 언어 콜백의 양쪽에서 더 쉽게 사용될 수 있다는 점을 제외하고는 성능에 영향을 미치지 않습니다.


6
"예외는 프로그래밍 언어에 관계없이 언어의 다른 기본 작업에 비해 항상 느립니다."... 예외 사용을 일반적인 흐름 제어로 컴파일하도록 설계된 언어를 제외하고는 예외입니다.
Ben Voigt

4
"예외 발생에는 할당 및 스택 해제가 모두 포함됩니다." 그것은 또한 일반적으로 사실이 아니며 다시 OCaml은 반대의 예입니다. 가비지 수집 된 언어에서는 소멸자가 없기 때문에 스택을 풀 필요가 없으므로 longjmp처리기로 이동합니다.
JD

2
@JonHarrop : 아마도 당신은 Pyhon이 예외 처리를위한 finally-clause를 가지고 있다는 것을 알지 못할 것입니다. 이것은 Python 구현에 스택 해제 기능이 있거나 Python이 아님을 의미합니다. 당신은 당신이 주장하는 (판타지) 주장하는 주제에 대해 완전히 무지한 것처럼 보입니다. 죄송합니다.
건배와 hth. - 알프

2
@ Cheersandhth.-Alf : "Pyhon에는 예외 처리를위한 finally-clause가 있습니다. 이는 Python 구현이 스택 해제 기능이 있거나 Python이 아님을 의미합니다." try..finally구조체 스택 풀림없이 구현 될 수있다. F #, C # 및 Java는 모두 try..finally스택 해제를 사용하지 않고 구현 됩니다. 당신은 longjmp(이미 설명했듯이) 핸들러 에게만 있습니다.
JD

4
@JonHarrop : 당신 은 딜레마를 제기하는 것처럼 들립니다 . 그러나 그것은 내가 지금까지 논의한 어떤 것과도 관련이 없으며 지금까지 당신은 부정적으로 들리는 말도 안되는 긴 시퀀스를 게시했습니다 . 나는 당신이 어떤 모호한 표현에 동의하거나 동의하지 않기 위해 당신을 믿어야 할 것입니다. 왜냐하면 당신은 적대자로서 당신이 그것이 "의미"를 드러내는 것을 선택하고 있기 때문입니다. 그리고 저는 그 모든 의미없는 말도 안되는 말, 반대 투표 등을 한 후에도 당신을 신뢰 하지 않습니다 .
건배와 hth. - 알프

13

코드를 어셈블리로 변환하거나 벤치 마크하지 않는 한 성능에 대해 주장 할 수 없습니다.

여기에 표시되는 내용이 있습니다. (빠른 벤치)

오류 코드는 발생 비율에 민감하지 않습니다. 예외는 던지지 않는 한 약간의 오버 헤드가 있습니다. 던지면 불행이 시작됩니다. 이 예에서는 0 %, 1 %, 10 %, 50 % 및 90 % 케이스에 대해 발생합니다. 90 %의 시간 동안 예외가 발생하면 코드는 예외가 발생하는 시간의 10 %보다 8 배 느립니다. 보시다시피 예외는 정말 느립니다. 자주 던지면 사용하지 마십시오. 응용 프로그램에 실시간 요구 사항이없는 경우 매우 드물게 발생하는 경우 자유롭게 처리하십시오.

당신은 그들에 대해 많은 모순 된 의견을 봅니다. 그러나 마지막으로 예외가 느린가요? 나는 판단하지 않는다. 벤치 마크를보십시오.

C ++ 예외 성능 벤치 마크


12

컴파일러에 따라 다릅니다.

예를 들어 GCC는 예외를 처리 할 때 성능이 매우 떨어지는 것으로 알려져 있었지만 지난 몇 년 동안 상당히 향상되었습니다.

그러나 예외 처리는 이름에서 알 수 있듯이 소프트웨어 설계의 규칙이 아니라 예외 여야합니다. 초당 너무 많은 예외를 발생시켜 성능에 영향을 미치는 애플리케이션이 있고 이것이 여전히 정상적인 작동으로 간주되는 경우, 작업을 다르게 수행하는 것에 대해 생각해야합니다.

예외는 복잡한 오류 처리 코드를 모두 제거하여 코드를 더 읽기 쉽게 만드는 좋은 방법이지만 정상적인 프로그램 흐름의 일부가 되 자마자 따라 가기가 정말 어려워집니다. a throw는 거의 goto catch변장 이라는 것을 기억하십시오 .


-1 "이것이 C ++ 98에서도 여전히 옳은가"라는 질문입니다. 확실히 컴파일러에 의존하지 않습니다. 또한이 대답 throw new Exception은 Java-ism입니다. 하나는 원칙적으로한다 결코 포인터를 던져하지 않습니다.
건배와 hth. -Alf

1
98 표준은 예외가 구현되는 방법을 정확히 지시합니까?
thecoshman

6
C ++ 98은 컴파일러가 아닌 ISO 표준입니다. 그것을 구현하는 많은 컴파일러가 있습니다.
Philipp

3
@thecoshman : 아니요. C ++ 표준은 구현 방법에 대해 언급하지 않습니다 (표준의 "구현 제한"부분을 제외하고).
In silico

2
@Insilico 그런 다음 (놀랍게도) 예외가 수행되는 방식이 구현 정의 (읽기, 컴파일러 특정)라는 논리적 결론을 내릴 수 있습니다.
thecoshman

4

예,하지만 그건 중요하지 않습니다. 왜?
이것을 읽으십시오 :
https://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx

기본적으로 Alexandrescu의 같은 사용하여 예외 (그들이 사용하기 때문에 50 배 둔화를 설명 것을 말한다 catch으로 else) 단지 잘못된 것입니다. 그것은 내가 C ++ 22를 바라는 것처럼 그것을 좋아하는 ppl을 위해 말하고 있습니다 :) 다음과 같은 것을 추가 할 것입니다 :
(기본적으로 컴파일러가 기존 코드에서 코드를 생성하기 때문에 이것은 핵심 언어가되어야합니다)

result = attempt<lexical_cast<int>>("12345");  //lexical_cast is boost function, 'attempt'
//... is the language construct that pretty much generates function from lexical_cast, generated function is the same as the original one except that fact that throws are replaced by return(and exception type that was in place of the return is placed in a result, but NO exception is thrown)...     
//... By default std::exception is replaced, ofc precise configuration is possible
if (result)
{
     int x = result.get(); // or result.result;
}
else 
{
     // even possible to see what is the exception that would have happened in original function
     switch (result.exception_type())
     //...

}

추신은 또한 예외가 그렇게 느리더라도 실행하는 동안 코드의 해당 부분에서 많은 시간을 소비하지 않으면 문제가되지 않습니다. 예를 들어 float 분할이 느리고 4x로 만드는 경우 FP 부문에 시간의 0.3 %를 소비해도 더 빠르지 만 ...


0

in silico는 구현에 따라 다르지만 일반적으로 예외는 모든 구현에서 느린 것으로 간주되며 성능 집약적 인 코드에서 사용해서는 안됩니다.

편집 : 나는 그것들을 전혀 사용하지 않는다고 말하는 것이 아니라 성능 집약적 코드의 경우 피하는 것이 가장 좋습니다.


9
이것은 예외 성능을 보는 매우 단순한 방법입니다. 예를 들어 GCC는 예외가 발생하지 않으면 성능 저하가 발생하지 않는 "제로 비용"구현을 사용합니다. 예외는 예외적 인 (즉, 드문 경우) 상황을 의미 하므로 일부 메트릭에 의해 느려도 여전히 사용하지 않을 이유가 충분하지 않습니다.
In silico

@insilico 내가 말한 이유를 보면 예외를 사용하지 말라고 말하지 않았습니다. 나는 성능 집약적 인 코드를 지정했고, 이것은 정확한 평가이며, 주로 gpgpus로 작업하고 예외를 사용하면 총에 맞습니다.
Chris McCabe
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.