다른 사람들이 지적했듯이 assert
결코 일어나지 않아야 할 프로그래머 실수에 대한 마지막 방어 요새입니다. 그것들은 당신이 발송할 때까지 좌우로 실패하지 않아야하는 위생 검사입니다.
또한 개발자가 유용하다고 생각하는 이유 (미학, 성능, 원하는 것)에 관계없이 안정적인 릴리스 빌드에서 생략 되도록 설계되었습니다 . 디버그 빌드와 릴리스 빌드를 구분하는 것의 일부이며, 릴리스 빌드에는 그러한 주장이 없습니다. 따라서 전 처리기 정의가 정의되어 있고 정의 되지 않은 릴리스 빌드를 시도 하는 유사한 "어설 션이있는 릴리스 빌드" 를 릴리스하려는 경우 디자인의 하위 버전 이 있습니다 . 더 이상 릴리스 빌드가 아닙니다._DEBUG
NDEBUG
디자인은 표준 라이브러리까지 확장됩니다. 수없이 많은 벡터를 구현하는 std::vector::operator[]
것 중 가장 기본적인 예로써 assert
, 벡터를 경계 밖으로 검사하지 않도록하기 위해 온 전성 검사를 수행합니다. 그리고 릴리스 빌드에서 이러한 검사를 활성화하면 표준 라이브러리가 훨씬 더 성능이 떨어지기 시작합니다. 의 벤치 마크 vector
사용operator[]
평범한 오래된 동적 배열에 대해 포함 된 주장이있는 채우기 ctor는 종종 이러한 검사를 비활성화 할 때까지 동적 배열이 상당히 빠르다는 것을 보여 주므로, 사소한 방식과는 거리가 멀지 만 성능에 영향을주는 경우가 많습니다. 여기에서 널 포인터 검사와 범위를 벗어난 검사는 그러한 검사가 스마트 포인터를 역 참조하거나 배열에 액세스하는 것처럼 코드 앞의 중요한 루프의 모든 프레임에 수백만 번 적용되는 경우 실제로 큰 비용이 될 수 있습니다.
따라서 주요 영역에서 이러한 온 전성 검사를 수행하는 릴리스 빌드를 원할 경우 작업에 대해 다른 도구와 릴리스 빌드에서 생략되지 않은 도구를 원할 것입니다. 내가 개인적으로 찾은 가장 유용한 것은 로깅입니다. 이 경우 사용자가 버그를보고하면 로그를 첨부하면 상황이 훨씬 쉬워지고 로그의 마지막 줄은 버그가 발생한 위치와 그 원인에 대한 큰 실마리를줍니다. 그런 다음 디버그 빌드에서 단계를 재현 할 때 마찬가지로 어설 션 오류가 발생하고 어설 션 오류로 인해 내 시간을 능률화 할 수있는 큰 단서가 생깁니다. 그러나 로깅은 상대적으로 비용이 많이 들기 때문에 일반적인 데이터 구조의 범위를 벗어나 배열에 액세스하지 않도록하는 것과 같이 매우 낮은 수준의 온 전성 검사를 적용하는 데 사용하지 않습니다.
그러나 마지막으로, 당신과 다소 동의하면, 알파 테스트 중에 디버그 빌드와 유사한 것을 테스터에게 실제로 전달하려는 합리적인 사례를 볼 수 있습니다 (예 : NDA에 서명 한 작은 알파 테스터 그룹) . 거기에서 테스터에게 소프트웨어를 실행하는 동안 실행할 수있는 테스트 및 더 자세한 출력과 같은 일부 디버깅 / 개발 기능과 함께 디버깅 정보가 첨부 된 정식 릴리스 빌드 이외의 것을 테스터에게 전달하면 알파 테스트가 간소화 될 수 있습니다. 나는 알파를 위해 그런 일을하는 몇몇 큰 게임 회사를 본 적이있다. 그러나 이것은 테스터에게 릴리스 빌드 이외의 것을 실제로 제공하려는 알파 또는 내부 테스트와 같은 것입니다. 실제로 릴리스 빌드를 제공하려는 경우 정의에 따라_DEBUG
"debug"와 "release"빌드의 차이점을 정말로 혼동하는 정의 된 것입니다.
출시 전에이 코드를 제거해야하는 이유는 무엇입니까? 검사는 성능 저하가 많지 않고 실패하면 더 직접적인 오류 메시지를 선호하는 문제가 있습니다.
위에서 지적한 바와 같이, 점검이 반드시 성능 관점에서 사소한 것은 아니다. 많은 사람들이 사소한 것 같지만, 표준 lib조차도 그것을 사용하고 std::vector
있으며, 최적화 된 릴리스 빌드로 간주되는 것에서 임의 액세스 통과의 시간이 4 배나 길 경우 많은 사람들에게 허용 할 수없는 방식으로 성능에 영향을 줄 수 있습니다 경계 검사로 인해 절대 실패하지 않아야합니다.
이전 팀에서는 실제로 행렬과 벡터 라이브러리가 디버그 빌드를 더 빠르게 실행하기 위해 특정 중요 경로에서 일부 어설트를 제외시켜야했습니다. 어설트는 수학 연산이 진행되는 시점까지 수학 연산 속도가 느려졌 기 때문입니다. 관심있는 코드를 추적하기 전에 15 분 정도 기다려야합니다. 제 동료들은 실제로asserts
그들은 그렇게하는 것만으로도 엄청난 차이가 생겼다는 사실을 알게 되었기 때문입니다. 대신 우리는 중요한 디버그 경로를 피하도록 만들었습니다. 이러한 중요한 경로가 경계 검사를 거치지 않고 벡터 / 매트릭스 데이터를 직접 사용하게하면 전체 작업을 수행하는 데 필요한 시간 (벡터 / 매트릭스 수학 이상 포함)이 몇 분에서 몇 초로 단축되었습니다. 따라서 극단적 인 경우이지만 확실히 성능 주장에서 무시할 수있는 것은 아니며 확실하지도 않습니다.
그러나 그것은 단지 asserts
설계된 방식 입니다. 그들이 전반적으로 그렇게 큰 성능 영향을 미치지 않았다면 디버그 빌드 기능 이상으로 설계되었거나 vector::at
릴리스 빌드에서도 경계 검사를 포함하고 범위를 벗어난 것을 사용 하는 경우 선호 할 수 있습니다 액세스 (예 : 아직 성능이 크게 저하됨). 그러나 현재 필자의 경우에 성능에 큰 영향을 미치면 NDEBUG
정의 할 때 생략되는 디버그 빌드 전용 기능으로 디자인이 훨씬 유용하다는 것을 알았습니다 . 적어도 내가 작업 한 경우 릴리스 빌드가 실제로 실패하지 않아야하는 위생 검사를 제외시키는 것은 큰 차이를 만듭니다.
vector::at
vs. vector::operator[]
이 두 가지 방법의 차이점은 예외뿐만 아니라 대안의 핵심이라고 생각합니다. 범위를 벗어난 벡터에 액세스하려고 할 때 범위를 벗어난 액세스가 쉽게 재현 할 수있는 오류를 유발하는지 확인하기위한 vector::operator[]
구현 assert
. 그러나 라이브러리 구현자는 최적화 된 릴리스 빌드에서 비용이 들지 않는다는 가정하에이를 수행합니다.
한편 vector::at
항상 경계가 체크 아웃을 수행하고 릴리스 빌드도에 던지는 제공하지만, 나는 종종 훨씬 더 코드를 사용하여 참조 점으로 IT 성능이 저하가됩니다 vector::operator[]
보다가 vector::at
. 많은 C ++ 디자인은 "사용 / 필요한 것에 대한 비용 지불"이라는 아이디어를 반영하며, 많은 사람들이 선호 operator[]
하는 경우가 많으며 , 릴리스 빌드의 경계 검사를 방해하지 않는 경우도 있습니다. 최적화 된 릴리스 빌드에서 경계 검사가 필요하지 않습니다. 릴리스 빌드에서 어설 션이 활성화 된 경우이 두 가지의 성능은 동일하며 벡터 사용은 항상 동적 배열보다 느려집니다. 따라서 어설 션의 디자인 및 이점 중 상당 부분은 릴리스 빌드에서 자유 로워진다는 아이디어를 기반으로합니다.
release_assert
이것은 이러한 의도를 발견 한 후에 흥미 롭습니다. 당연히 모든 사람의 사용 사례는 다를 수 있지만release_assert
확인을 수행하고 릴리스 빌드에서도 라인 번호와 오류 메시지를 표시하는 소프트웨어에 충돌을 합니다.
예외가 발생했을 때처럼 소프트웨어가 정상적으로 복구되기를 원하지 않는 경우에는 모호한 경우가 있습니다. 이러한 경우 릴리스에서도 충돌이 발생하여 소프트웨어가 절대 발생하지 않아야 할 무언가를 발견했을 때 사용자에게 줄 번호를 줄 수 있기를 원합니다. 여전히 외부 입력 오류가 아닌 프로그래머 오류에 대한 위생 검사 영역에서 예외는 있지만 출시 비용에 대해 걱정하지 않고 충분히 저렴합니다.
실제로 줄 번호와 오류 메시지 가 발생하여 예외를 정상적으로 복구하는 것 보다 오류 가 발생하여 릴리스에 보관하기에 충분히 저렴한 경우가 있습니다. 또한 기존 예외에서 복구하려고 할 때 발생한 오류와 같이 예외에서 복구 할 수없는 경우가 있습니다. 필자 release_assert(!"This should never, ever happen! The software failed to fail!");
는 처음에는 예외적 인 경로 내에서 검사가 수행되고 정상적인 실행 경로에서 비용이 들지 않기 때문에 먼지가 저렴하고 자연스럽게 완벽하게 적합하다는 것을 알았 습니다.