メモリリークを検出するためにどの戦略を使用しますか?
この質問は、主にC++に焦点を当てたインタビューで尋ねられましたが、答えを見つけることができませんでした。それを実行するツールはあり、それらを使用するつもりだと私は言ったが、それは満足できるものではなく、私には他に何も考えられなかった。
後でインターネットで回答を検索したところ、さまざまなツールの使用方法についての参考資料しか見つかりませんでした。それらが使用する戦略ではありませんでした。
「どのタイプのメモリリークですか?」 :)
メモリリークにはさまざまな種類があります-明らかに、観察および検出するのが簡単なものもあれば、別の極端なものもあります。リークの種類については この投稿 をご覧ください。
リークを検出するためのツールがいくつかあります。それらのほとんどは、DevPartner BoundscheckerやValgrindなどの動的分析ツールです。一部の静的分析ツールは、メモリリークのいくつかの(些細な)ケースを検出する場合もあります。
メモリリークディテクタを作成する必要がある場合は、すべてのメモリの割り当てと割り当て解除を追跡する必要があります。これはさまざまな方法で実行できます。システムAPI呼び出しを直接フックするか、コード(ソースコード、中間コード、さらにはマシン実行可能コード)を計測して、割り当て/割り当て解除を監視します。
個人的に私の答えは:
最初に、すべての警告が有効になっていることを確認することにより、コンパイラーが提供する警告を使用します。 実際には、コンパイラの警告をすべてオンにし、ゼロの警告を要求するコーディング標準を使用することをお勧めします。
また、CoverityやPCLintなどのツールを使用して静的分析を実行することも強く検討します。これは、このような潜在的な問題を見つけるのに優れているためです。
次に、プロファイリングツールの下でテストスイート全体を実行して、可能な限り100%に近いカバレッジを確保し、次にValgrindなどのチェッカー、またはこれがサポートされている場合はデバッガーの下でも実行します。
最後に、おそらくツールチェーンとターゲットがそのようなツールをサポートしていない場合で、ライブラリを購入してこれを行うことができなかった場合、自分で独自のツールを実装することを検討します。テスト用または製品のバックグラウンドコンポーネントとしての特別なビルド。
それから初めて、拡大するように求められた場合、私はこれをどのように実装するかについて始めます。
なぜこの戦略costを返答するのかと尋ねられた場合、コンパイラの作成者は、メモリリークなどの問題に対処するために、何千人もの時間を費やしてきました。静的分析ツールの開発者とその両方が、コードを100%カバーしています。 Valgrindなどのツールは優れた機能を果たしますが、監視中にコードが100%実行された場合のみです。したがって、プロファイリングツールを使用してカバレッジを確立します。
一般に、メモリプロファイリングツールは、ほとんどの場合、通常、かなりのオーバーヘッドとなり、ガベージコレクションを有効化、インストール、または有効化する方が効果的であるため、製品コードには属していません。
彼らはあなたにRAIIについて言及するように求めていたかもしれません Resource Acquisition Is Initialization ですが、厳密にはそれはメモリリークを検出するのではなく回避するための戦略です。
2つの主要な戦略があります。
リークはmalloc
なしのfree
です(C++ではnew
なしのdelete
です)。無料のグローバルmallocは一般に無料である必要がないため、安価です。プログラムの存続期間の最後にプロセスをクリーンアップすると、ツリーを検索してポインターを探し、それをフリーリストに追加するよりも、より速く、より速くなります。しかし、ネストされた関数内のmallocsはかなり頻繁に呼び出される可能性があり、このメモリは使い果たされます。スタック上のalloca
はまったく解放されません。それがスタックの美しさです。
Malloc/freeフックを追加するだけでは、リークの検出には機能しません。最後に、収集されたフック統計を照会する必要があります。境界チェッカーなどのコンパイル時ツールを使用しても、リーク検出には機能しません。これは、mallocされた各ポインターをマークおよびカウントし、実行時に一致する空きを監視する必要があります。
-fsanitize=leak
または-fsanitize=address
およびASAN_OPTIONS:
detect_leaks=1
のように、コンパイル時にmalloc/freeフックを追加します。これはもちろんはるかに高速ですが、特に必要です。準備されたバイナリ。動的言語でははるかに簡単です。VMメモリ割り当て呼び出しを簡単にフックし、プログラムの最後に欠落している空き呼び出しを報告できます。
私はあなたが正しい軌道に乗っていたと思います。単純な答えは「監視」であり、それが会話の良い出発点です。私は次のようなことを言ったでしょう:
マシン上のすべてのプログラムのメモリ割り当てを報告する監視ツールを実装します。次に、アラートの重大度に応じて、通話中の担当者にテキスト/電話を送信するアラートシステムを実装します。
彼らはあなたに特定のツール、またはおそらくそのツールがどのように機能するかについて言及することを探していたのでしょうか?
あなたはたまたまDevOpsのポジションに応募していたのではないでしょうか? :)
とにかく、メモリリークの質問に戻ります。これを行う最も簡単な方法は、.NETプロファイラーのようなツールを実装することです。このツールは、基本的にコードを表示し、以下に関するメトリックをレポートします。
私は過去に New Relic を使用しました(これはAPM製品です)。これは素晴らしいツールです。
そして、はい、おそらく、この種のツールを使用することによって得られるわずかな「寄生」効果がありますが、私の意見では、利点はそれをはるかに上回ります。
また、独自のツールをどのように作成するかと聞かれた場合は、まずホイールを再発明するので、努力する価値がないと最初に説明します。しかし、それらが本当にを知っている必要がある場合、実行中のすべてのアプリを監視し、それらを報告するマシンにインストールされるエージェントを作成すると言います使用量は、後で報告される可能性がある一部のストレージ/ APIにバックアップされます。ただし、その場合は、レポートツールを作成して、それを何らかのアラートツールに関連付ける必要もあります。ただし、これらの種類のものの価格に見合うだけの価値がある既存のツールがあります。
結局、私はツールのアプローチをダブルダウンしたでしょう。もし彼らがそれを気に入らなかったら、多分彼らはむしろそれを自分で書くのでしょうか?しかし、正直なところ、それは努力に値するものではなく、ツールを購入するだけではなく、そのようなものの開発/維持に多くの費用を費やすでしょう。そして、もしあなたがおそらくとにかく弾丸をかわしたよりも彼らがその頑固であるならば。
さまざまなツール、環境警告、および静的メモリ構造への移行は別として、ここで探していたのは、メモリが割り当てられたときに何らかのカウンタがインクリメントされ、メモリがデクリメントされたときにある種の参照カウントメカニズムであると推測できます。割り当て解除。
これは、どのプラットフォームでも機能する移植可能な方法であり、それ以上のツールは必要ありません。
メモリリーク検出の戦略は、パフォーマンステストです。パフォーマンステストは、アプリケーションの制限を見つけるためだけでなく、N時間にわたる定常状態の負荷も含める必要があります。たとえば、アプリケーションを負荷のある状態で4時間実行します。
開発プロセスの一環としてこれを行うと、リリース前にメモリリークを特定して修正できます。
リークが特定されたら、WINDBGなどのツールを使用して問題のあるメソッドを見つけ、修正を適用できます。
メモリリークを検出する方法はいくつかあります。
私は、意図が特にを探すことであり、一般的な衛生設備だけではなく、漏れを探すことであったと仮定します。また、意図はあなたのコーディング能力を評価することであると想定します。そのため、それは単に「valgrindを使用する」(または他のツール)だけでなく、ツール自体を記述するために取る一般的なアプローチについてです。
この場合、比較的単純な開始点は、メモリアロケーターが呼び出されるたびにスタックトレースに移動し、割り当てられた各ブロックに関連付けられたスタックトレースを(割り当てサイズやアドレスなどの他の明らかな「もの」とともに)保持することです。割り当てたブロックの)。ブロックが解放されると、そのブロックに関連付けられているスタックトレースが削除されます。
プログラムがシャットダウンすると、テーブルに残っているスタックトレースがすべてダンプされます。毎回スタックをダンプしたので、解放されなかったブロックを割り当てたコードの一部だけでなく、そのポイントに到達するために使用された呼び出しの完全なシーケンスのトレースを提供できます。
ほとんどの場合、メモリリークの検出とともに、少なくともメモリブロックの終わりを超えた書き込みを処理します。これを行うには、通常、要求されたよりも大幅に大きいブロックを割り当て、既知のパターンを追加のメモリに書き込み、クライアントにブロックの中央のアドレスを割り当てます。次に、プログラムの最後で、メモリに予想されるパターンとは異なる値が含まれている場合は、割り当てられた部分の外側に書き込まれたことがわかります。