他の人が指摘したassert
ように、プログラマーのミスは絶対に起こらないようにするための最後の防衛の砦です。これらは健全性チェックであり、出荷時に左右に失敗しないことを期待しています。
また、安定性のあるリリースビルドから除外するように設計されています。これは、開発者が便利だと思う理由(美学、パフォーマンス、好きなもの)に関係ありません。これは、デバッグビルドとリリースビルドを分離するものの一部であり、定義により、リリースビルドにはそのようなアサーションがありません。したがって、アナロジー的な「アサーションを備えたリリースビルド」をリリースしたい場合、デザインのサブバージョンがあります。これは、_DEBUG
プリプロセッサ定義が定義されていないリリースビルドの試みNDEBUG
です。もはやリリースビルドではありません。
デザインは標準ライブラリにも拡張されます。多数の中の非常に基本的な例として、多くの実装でstd::vector::operator[]
はassert
、範囲外のベクトルをチェックしていないことを確認するための健全性チェックが行われます。また、リリースビルドでこのようなチェックを有効にすると、標準ライブラリのパフォーマンスが大幅に低下します。vector
使用のベンチマークoperator[]
そして、単純な古い動的配列に対してそのようなアサーションを含むフィルクターは、そのようなチェックを無効にするまで動的配列がかなり高速であることを示すことが多いため、些細な方法からはほど遠いパフォーマンスに影響を与えることがよくあります。ここでのヌルポインターチェックと境界外チェックは、スマートポインターの逆参照や配列へのアクセスなどの単純なコードに先行するクリティカルループ内のすべてのフレームでこのようなチェックが何百万回も適用されている場合、実際に莫大な費用になる可能性があります。
そのため、重要な領域でこのような健全性チェックを実行するリリースビルドが必要な場合は、ジョブに別のツールと、リリースビルドから除外するように設計されていないツールを希望する可能性が高くなります。私が個人的に見つけた最も有用なものは、ロギングです。その場合、ユーザーがバグを報告するときに、ログを添付すると事態がずっと簡単になり、ログの最後の行からバグの発生場所とその可能性に関する大きな手がかりが得られます。次に、デバッグビルドで手順を再現すると、同様にアサーションエラーが発生する可能性があり、そのアサーションエラーにより、時間を効率化するための大きな手がかりが得られます。ただし、ロギングは比較的高価なので、汎用データ構造内の範囲外で配列にアクセスしないようにするなど、極端に低レベルの健全性チェックを適用するために使用しません。
それでも最後に、そしてある程度あなたと同意して、アルファテスト中にデバッグビルドに似たものをテスターに実際に渡したい合理的なケースを見ることができました、例えば、NDAに署名したアルファテスターの小さなグループと。そこで、実行可能なテストやソフトウェア実行中のより詳細な出力などのデバッグ/開発機能とともにデバッグ情報が添付されたフルリリースビルド以外の何かをテスターに渡すと、アルファテストが合理化される可能性があります。少なくともいくつかの大手ゲーム会社がアルファのためにそのようなことをしているのを見てきました。しかし、それはアルファ版や内部テストのようなもので、テスターにリリースビルド以外の何かを提供しようとしているのです。実際にリリースビルドを出荷しようとしている場合は、定義上、_DEBUG
定義されているか、そうでなければ「デバッグ」ビルドと「リリース」ビルドの違いを本当に混乱させています。
リリース前にこのコードが削除されるのはなぜですか?チェックはパフォーマンスの低下とは言えません。チェックに失敗した場合は、間違いなく直接的なエラーメッセージを希望します。
上記で指摘したように、チェックは必ずしもパフォーマンスの観点から些細なものではありません。多くはささいなものですが、標準ライブラリでも使用されているため、多くの場合std::vector
、最適化されたリリースビルドであると想定されるランダムアクセストラバーサルの4倍の時間がかかると、多くの場合、許容できない方法でパフォーマンスに影響を与える可能性があります境界チェックのため、失敗することはありません。
前のチームでは、デバッグビルドを高速に実行するために、特定のクリティカルパスの一部のアサートをマトリックスとベクトルライブラリから除外する必要がありました。目的のコードを追跡する前に、15分間待機する必要があります。私の同僚は実際にasserts
ただそれをするだけで大きな違いが生じるとわかったからです。代わりに、クリティカルデバッグパスがそれらを避けるようにするだけで解決しました。これらのクリティカルパスで境界チェックを経由せずにベクター/マトリックスデータを直接使用するようにした場合、完全な操作(ベクター/マトリックス演算以上のものを含む)を実行するために必要な時間が数分から秒に短縮されました。したがって、それは極端な場合ですが、間違いなく、パフォーマンスの観点から断言が常に無視できるわけではなく、近いというわけでもありません。
しかし、それはまさにasserts
設計された方法でもあります。全体にそれほど大きなパフォーマンスへの影響がなかった場合、デバッグビルド機能以上のものとして設計されている場合、またはvector::at
リリースビルドでも境界チェックを含むものを使用し、範囲外でスローする場合、私はそれを好むかもしれませんアクセス(例:パフォーマンスが大幅に低下します)。しかし、現時点でNDEBUG
は、定義時に省略されるデバッグビルドのみの機能として、私の場合のパフォーマンスへの大きな影響を考慮して、デザインがはるかに有用であると感じています。少なくとも私が取り組んできたケースでは、リリースビルドが実際に失敗することのない健全性チェックを除外することで、大きな違いが生じます。
vector::at
対 vector::operator[]
これらの2つの方法の違いは、これと代替の例外の中心にあると思います。vector::operator[]
通常assert
、実装は、境界外のベクトルにアクセスしようとしたときに、境界外アクセスが簡単に再現可能なエラーをトリガーすることを確認します。しかし、ライブラリの実装者は、最適化されたリリースビルドで1ドルもかかりませんという前提でこれを行います。
一方vector::at
、リリースビルドでも常に範囲外のチェックとスローを行いますが、パフォーマンスのペナルティがあり、使用するコードvector::operator[]
よりもはるかに多くのコードを使用することがよくありますvector::at
。C ++の設計の多くは、「使用/必要に応じて支払う」という考え方を反映しており、多くの人がよく支持しoperator[]
ます。最適化されたリリースビルドで境界チェックを行う必要はありません。突然、リリースビルドでアサーションが有効になった場合、これら2つのパフォーマンスは同じになり、ベクトルの使用は常に動的配列よりも遅くなります。したがって、アサーションの設計と利点の大部分は、リリースビルドでアサーションがフリーになるという考えに基づいています。
release_assert
これらの意図を発見した後、これは興味深いです。当然、全員のユースケースは異なりますrelease_assert
が、チェックを行い、リリースビルドでもソフトウェアをクラッシュさせて行番号とエラーメッセージを表示する方法を使用すると思います。
私の場合、例外がスローされた場合のようにソフトウェアを正常に回復させたくないという不明瞭なケースになります。そのような場合はリリースでもクラッシュして、ソフトウェアが発生しないはずの何かに遭遇したときに報告する行番号をユーザーに与えることができます。例外はありますが、リリースのコストを気にせずに実行できるほど安価です。
私は行番号とエラーメッセージとハードクラッシュ見つけるだろういくつかの例実際に存在することが好ましい優雅リリースに保つために安価に十分であるかもしれないスローされた例外からの回復には。また、既存のものから回復しようとしたときにエラーが発生したなど、例外から回復できない場合があります。そこrelease_assert(!"This should never, ever happen! The software failed to fail!");
では、最初に例外的なパス内でチェックが実行され、通常の実行パスでは何も費用がかからないので、当然ながらダート安い安価なものに最適です。