저는 C ++의 체계적인 오류 처리를 보고있었습니다. Andrei Alexandrescu 는 C ++의 예외가 매우 느리다고 주장합니다.
C ++ 98에서도 여전히 그렇습니까?
저는 C ++의 체계적인 오류 처리를 보고있었습니다. Andrei Alexandrescu 는 C ++의 예외가 매우 느리다고 주장합니다.
C ++ 98에서도 여전히 그렇습니까?
답변:
오늘날 예외에 사용되는 주요 모델 (Itanium ABI, VC ++ 64 비트)은 비용이 0 인 모델 예외입니다.
아이디어는 가드를 설정하고 모든 곳에서 예외가 있는지 명시 적으로 확인하여 시간을 낭비하는 대신 컴파일러가 예외 (프로그램 카운터)를 throw 할 수있는 지점을 핸들러 목록에 매핑하는 부가 테이블을 생성한다는 것입니다. 예외가 발생하면이 목록을 참조하여 올바른 핸들러 (있는 경우)를 선택하고 스택이 풀립니다.
일반적인 if (error)전략과 비교 :
if예외 발생시 약 10 배 / 20 배 비용그러나 비용은 측정하기가 간단하지 않습니다.
dynamic_cast각 핸들러에 대한 테스트)따라서 대부분 캐시 미스이므로 순수한 CPU 코드에 비해 사소하지 않습니다.
참고 : 자세한 내용은 TR18015 보고서, 5.4 장 예외 처리 (pdf)를 참조하십시오.
예, 예외는 예외적 인 경로 에서 느리지 만 if일반적 으로 명시 적 검사 ( 전략) 보다 빠릅니다 .
참고 : Andrei Alexandrescu는이 "빠른"질문을하는 것 같습니다. 저는 개인적으로 상황이 양방향으로 바뀌는 것을 보았습니다. 일부 프로그램은 예외가있는 경우 더 빠르며 다른 프로그램은 브랜치에서 더 빠르기 때문에 실제로 특정 조건에서 최적화 가능성이 손실되는 것 같습니다.
그게 그렇게 중요한 건가 ?
나는 그렇지 않다고 주장 할 것이다. 프로그램은 성능이 아닌 가독성 을 염두에두고 작성해야합니다 (적어도 첫 번째 기준은 아닙니다). 예외는 호출자가 그 자리에서 실패를 처리 할 수 없거나 처리하기를 원하지 않을 것으로 예상하고 스택 위로 전달하는 경우에 사용됩니다. 보너스 : C ++ 11에서는 표준 라이브러리를 사용하여 스레드간에 예외를 마샬링 할 수 있습니다.
이것은 미묘하지만 map::find던지지 않아야한다고 주장 하지만 null이기 때문에 역 참조 시도가 실패하면 던지는 map::finda checked_ptr를 반환 해도 괜찮 습니다. 후자의 경우 Alexandrescu가 소개 한 클래스의 경우와 같이 호출자가 선택합니다. 명시 적 검사와 예외 의존 사이. 더 많은 책임을주지 않고 발신자에게 권한을 부여하는 것은 일반적으로 좋은 디자인의 신호입니다.
abort하면 바이너리 크기 풋 프린트를 측정하고로드 시간 / i- 캐시가 유사하게 작동하는지 확인할 수 있습니다. 당연히, 어떤 것도 치지 않는 것이 좋습니다 abort...
질문이 게시되었을 때 나는 택시를 기다리고 의사에게가는 중이었기 때문에 짧은 코멘트를 할 시간이있었습니다. 그러나 이제 댓글을 달고 찬성 및 반대 투표를 했으므로 내 답변을 추가하는 것이 좋습니다. Matthieu의 대답이 이미 꽤 좋다고 해도 .
다시 주장
"저는 C ++의 체계적인 오류 처리를 보고있었습니다. Andrei Alexandrescu 는 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 .
정확성 은 거의 항상 효율성보다 우선합니다.
예외없이 다음과 같은 일이 쉽게 발생할 수 있습니다.
일부 코드 P는 리소스를 얻거나 일부 정보를 계산하기위한 것입니다.
호출 코드 C는 성공 / 실패를 확인해야했지만 그렇지 않습니다.
존재하지 않는 리소스 또는 유효하지 않은 정보가 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 ++ 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 언어 콜백의 양쪽에서 더 쉽게 사용될 수 있다는 점을 제외하고는 성능에 영향을 미치지 않습니다.
longjmp처리기로 이동합니다.
try..finally구조체 스택 풀림없이 구현 될 수있다. F #, C # 및 Java는 모두 try..finally스택 해제를 사용하지 않고 구현 됩니다. 당신은 longjmp(이미 설명했듯이) 핸들러 에게만 있습니다.
코드를 어셈블리로 변환하거나 벤치 마크하지 않는 한 성능에 대해 주장 할 수 없습니다.
여기에 표시되는 내용이 있습니다. (빠른 벤치)
오류 코드는 발생 비율에 민감하지 않습니다. 예외는 던지지 않는 한 약간의 오버 헤드가 있습니다. 던지면 불행이 시작됩니다. 이 예에서는 0 %, 1 %, 10 %, 50 % 및 90 % 케이스에 대해 발생합니다. 90 %의 시간 동안 예외가 발생하면 코드는 예외가 발생하는 시간의 10 %보다 8 배 느립니다. 보시다시피 예외는 정말 느립니다. 자주 던지면 사용하지 마십시오. 응용 프로그램에 실시간 요구 사항이없는 경우 매우 드물게 발생하는 경우 자유롭게 처리하십시오.
당신은 그들에 대해 많은 모순 된 의견을 봅니다. 그러나 마지막으로 예외가 느린가요? 나는 판단하지 않는다. 벤치 마크를보십시오.
컴파일러에 따라 다릅니다.
예를 들어 GCC는 예외를 처리 할 때 성능이 매우 떨어지는 것으로 알려져 있었지만 지난 몇 년 동안 상당히 향상되었습니다.
그러나 예외 처리는 이름에서 알 수 있듯이 소프트웨어 설계의 규칙이 아니라 예외 여야합니다. 초당 너무 많은 예외를 발생시켜 성능에 영향을 미치는 애플리케이션이 있고 이것이 여전히 정상적인 작동으로 간주되는 경우, 작업을 다르게 수행하는 것에 대해 생각해야합니다.
예외는 복잡한 오류 처리 코드를 모두 제거하여 코드를 더 읽기 쉽게 만드는 좋은 방법이지만 정상적인 프로그램 흐름의 일부가 되 자마자 따라 가기가 정말 어려워집니다. a throw는 거의 goto catch변장 이라는 것을 기억하십시오 .
throw new Exception은 Java-ism입니다. 하나는 원칙적으로한다 결코 포인터를 던져하지 않습니다.
예,하지만 그건 중요하지 않습니다. 왜?
이것을 읽으십시오 :
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 %를 소비해도 더 빠르지 만 ...
in silico는 구현에 따라 다르지만 일반적으로 예외는 모든 구현에서 느린 것으로 간주되며 성능 집약적 인 코드에서 사용해서는 안됩니다.
편집 : 나는 그것들을 전혀 사용하지 않는다고 말하는 것이 아니라 성능 집약적 코드의 경우 피하는 것이 가장 좋습니다.