54

OS や組み込みシステムの一部をプログラミングしている場合を除き、そうする理由はありますか? 頻繁に作成および破棄されるいくつかの特定のクラスでは、メモリ管理関数をオーバーロードしたり、オブジェクトのプールを導入したりすることでオーバーヘッドが低下する可能性があると想像できますが、これらのことをグローバルに行うのですか?

さらに
、オーバーロードされた削除関数にバグが見つかりました - メモリが常に解放されるわけではありませんでした。そして、それはそれほどメモリが重要ではないアプリケーションでした。また、これらのオーバーロードを無効にすると、パフォーマンスが最大 0.5% しか低下しません。

4

16 に答える 16

80

多くの理由で、私が作業しているグローバルな new および delete 演算子をオーバーロードします。

  • すべての小さな割り当てをプールする -- オーバーヘッドを減らし、断片化を減らし、小さな割り当てが多いアプリのパフォーマンスを向上させることができます
  • 有効期間がわかっている割り当てのフレーミング-- この期間が終了するまですべての解放を無視し、次にすべてをまとめて解放します (確かに、グローバルよりもローカルの演算子のオーバーロードでこれを行います)。
  • アライメント調整 -- キャッシュライン境界など
  • alloc fill -- 初期化されていない変数の使用法を公開するのに役立ちます
  • free fill -- 以前に削除されたメモリの使用状況を明らかにするのに役立ちます
  • フリーの遅延-- フリー フィルの有効性が向上し、パフォーマンスが向上する場合があります
  • センチネルまたはフェンスポスト-- バッファー オーバーラン、アンダーラン、および時折発生するワイルド ポインターを明らかにするのに役立ちます
  • 割り当てのリダイレクト-- NUMA、特別なメモリ領域を考慮したり、個別のシステムをメモリ内で分離しておくこともできます (埋め込みスクリプト言語や DSL など)。
  • ガベージ コレクションまたはクリーンアップ -- これらの埋め込みスクリプト言語に再び役立ちます
  • ヒープ検証-- N 個の allocs/frees ごとにヒープ データ構造を調べて、すべてが正常に見えることを確認できます。
  • リーク追跡使用状況のスナップショット/統計(スタック、割り当て期間など)を含むアカウンティング

新規/削除アカウンティングの考え方は非常に柔軟で強力です。たとえば、割り当てが発生するたびにアクティブなスレッドのコールスタック全体を記録し、それに関する統計を集計できます。何らかの理由でスタック情報をローカルに保持するスペースがない場合は、ネットワーク経由でスタック情報を送信できます。ここで収集できる情報の種類は、想像力 (およびもちろんパフォーマンス) によってのみ制限されます。

グローバル オーバーロードを使用するのは、そこに多くの一般的なデバッグ機能を掛けたり、同じオーバーロードから収集した統計に基づいてアプリ全体を大幅に改善したりするのに便利だからです。

個々の型にもカスタム アロケーターを使用しています。多くの場合、カスタム アロケータを提供することで得られるスピードアップや機能は、たとえば STL データ構造の単一の使用ポイントに提供することで得られるものであり、グローバル オーバーロードから得られる一般的なスピードアップをはるかに超えています。

C/C++ 用に出回っているアロケーターとデバッグ システムのいくつかを調べてみると、次のアイデアやその他のアイデアがすぐに思いつくでしょう。

(古いが影響力のある本の 1 つは、 Writing Solid Codeで、C でカスタム アロケーターを提供する理由の多くが説明されており、そのほとんどは今でも非常に関連性があります。)

これらの優れたツールのいずれかを使用できる場合は、自分で作成するよりも使用したいと思うでしょう。

それがより速く、より簡単で、ビジネス/法的な面倒が少ない、プラットフォームでまだ何も利用できない、またはより有益な状況があります: グローバルなオーバーロードを掘り下げて記述します。

于 2009-08-01T04:07:57.733 に答える
26

new と delete をオーバーロードする最も一般的な理由は、単純にメモリ リークとメモリ使用統計をチェックするためです。「メモリリーク」は通常、メモリエラーに一般化されていることに注意してください。二重削除やバッファ オーバーランなどをチェックできます。

その後の使用は、通常、ガベージ コレクションプーリングなどのメモリ割り当てスキームです。

他のすべてのケースは、他の回答(ディスクへのロギング、カーネルの使用)で言及されている特定のものです。

于 2009-07-20T09:22:56.447 に答える
15

メモリのタグ付けなど、ここで説明したその他の重要な用途に加えて、アプリ内のすべての割り当てを強制的に固定ブロック割り当てにする唯一の方法でもあります。これは、パフォーマンスと断片化に大きな影響を与えます。

たとえば、ブロック サイズが固定された一連のメモリ プールがあるとします。グローバルをオーバーライドnewすると、すべての 61 バイト割り当てを、たとえば 64 バイト ブロックを含むプールに、すべての 768 ~ 1024 バイト割り当てを 1024b ブロック プールに、それより上のすべてのものを 2048 バイト ブロック プールに、およびそれより大きいものすべてに割り当てることができます。一般的な不規則なヒープに 8kb より。

固定ブロック アロケーターは、ヒープから意のままに割り当てるよりもはるかに高速で、断片化する可能性が低いため、これにより、くだらないサード パーティ コードでもプールから割り当てるように強制でき、アドレス空間全体にうんざりすることはありません。

これは、ゲームなど、時間とスペースが重要なシステムでよく行われます。280Z28、Meeh、および Dan Olson がその理由を説明しています。

于 2009-07-31T01:31:03.683 に答える
10

UnrealEngine3 は、コア メモリ管理システムの一部としてグローバルな new と delete をオーバーロードします。さまざまな機能 (プロファイリング、パフォーマンスなど) を提供する複数のアロケーターがあり、それを通過するにはすべての割り当てが必要です。

編集: 私自身のコードでは、最後の手段としてのみ実行します。つまり、私はほとんど積極的にそれを使用しないということです。しかし、私の個人的なプロジェクトは明らかにはるかに小さい/非常に異なる要件です。

于 2009-07-20T09:13:35.433 に答える
6

一部のリアルタイム システムでは、初期化後に使用されないようにオーバーロードされています。

于 2009-07-20T09:16:26.630 に答える
4

new&deleteをオーバーロードすると、メモリ割り当てにタグを追加できます。システムまたはコントロールごと、またはミドルウェアごとに割り当てにタグを付けます。実行時に、それぞれがどれだけ使用しているかを確認できます。UIから分離されたパーサーの使用法や、ミドルウェアが実際に使用している量を確認したいのかもしれません。

また、割り当てられたメモリの周りにガードバンドを配置するために使用することもできます。アプリがクラッシュした場合は、アドレスを確認できます。内容が「0xABCDABCD」(またはガードとして選択したもの)として表示されている場合は、所有していないメモリにアクセスしています。

おそらく、deleteを呼び出した後、このスペースを同様に認識可能なパターンで埋めることができます。VisualStudioはデバッグでも同様のことをすると思います。初期化されていないメモリを0xCDCDCDCDでいっぱいにしませんか?

最後に、断片化の問題がある場合は、それを使用してブロックアロケータにリダイレクトできますか?これが本当に問題になる頻度はわかりません。

于 2009-07-30T20:42:36.180 に答える
3

環境で new と delete の呼び出しが機能しない場合は、それらをオーバーロードする必要があります。

たとえば、カーネル プログラミングでは、デフォルトの new と delete は、ユーザー モード ライブラリに依存してメモリを割り当てるため、機能しません。

于 2009-07-20T09:35:21.443 に答える
2

アプリケーションがランダム クラッシュ以外の方法でメモリ不足の状態に対応できるようにすると、便利なトリックになる場合があります。これを行うには、失敗をキャッチし、いくつかのものを解放して再試行するnewデフォルトへの単純なプロキシにすることができます。new

最も単純な手法は、まさにその目的のために、起動時にメモリの空白ブロックを予約することです。利用できるキャッシュもあるかもしれません - 考え方は同じです。

最初の割り当てエラーが発生したとき、メモリ不足の状態についてユーザーに警告する時間はまだあります (「もう少し生き残ることができますが、作業を保存して他のアプリケーションを閉じてください」)。状態をディスクに保存したり、サバイバル モードに切り替えたり、状況に応じて適切な方法を選択したりできます。

于 2009-07-31T19:48:31.807 に答える
2

実用的な観点からは、システム ライブラリ レベルで malloc をオーバーライドする方がよい場合があります。これは、オペレータ new がとにかくそれを呼び出す可能性があるためです。

Linux では、次の例のように、システムの malloc の代わりに独自のバージョンの malloc を配置できます。

http://developers.sun.com/solaris/articles/lib_interposers.html

その記事では、彼らはパフォーマンス統計を収集しようとしています。ただし、free もオーバーライドすると、メモリ リークも検出される可能性があります。

LD_PRELOAD を使用して共有ライブラリでこれを行っているため、アプリケーションを再コンパイルする必要さえありません。

于 2009-07-28T16:24:54.230 に答える
2

C++ で記述された Photoshop プラグインは、operator newPhotoshop 経由でメモリを取得するようにオーバーライドする必要があります。

于 2009-07-30T20:15:27.343 に答える
2

メモリに書き込まれたデータが自動的にディスクにも保存されるように、メモリ マップされたファイルでそれを行いました。
また、メモリがマップされた IO デバイスがある場合、または連続したメモリの特定のブロックを割り当てる必要がある場合に、特定の物理アドレスでメモリを返すためにも使用されます。

しかし、99% の時間は、メモリがいつ、どこで、どのくらいの頻度で割り当てられ、解放されるかをログに記録するデバッグ機能として実行されます。

于 2009-07-30T20:16:30.257 に答える
2

*「セキュリティ」上の理由から、割り当て解除時に使用したすべてのメモリを上書きする必要があるシステムで実行されたのを見てきました。このアプローチは、削除時にゼロで上書きされるブロック全体のサイズを含む、メモリの各ブロックの開始時に余分な数バイトを割り当てることでした。

ご想像のとおり、これには多くの問題がありましたが、(ほとんどの場合) 機能し、チームはかなり大きな既存のアプリケーションですべてのメモリ割り当てを確認する必要がなくなりました。

確かにそれが良い使い方だと言っているわけではありませんが、おそらく最も想像力に富んだものの1つです...

*悲しいことに、セキュリティの外観ほど実際のセキュリティについてではありませんでした...

于 2009-07-28T16:56:17.317 に答える
2

ゲームがシステムから 1 つの巨大なメモリ チャンクを割り当て、オーバーロードされた new と delete を介してカスタム アロケータを提供することは、実際にはかなり一般的です。大きな理由の 1 つは、コンソールのメモリ サイズが固定されているため、リークと断片化の両方が大きな問題になることです。

通常、(少なくともクローズド プラットフォームでは) デフォルトのヒープ操作には、制御の欠如とイントロスペクションの欠如が伴います。多くのアプリケーションではこれは問題ではありませんが、ゲームが固定メモリの状況で安定して動作するためには、追加の制御とイントロスペクションの両方が非常に重要です。

于 2009-07-31T01:05:06.883 に答える
0

最も一般的な使用例は、おそらくリークチェックです。

もう1つの使用例は、使用している標準ライブラリでは満たされない、環境内のメモリ割り当てに関する特定の要件がある場合です。たとえば、マルチスレッド環境でメモリ割り当てがロックされていないことを保証する必要があります。

于 2009-08-03T09:31:20.103 に答える
0

多くの人がすでに述べているように、これは通常、パフォーマンスが重要なアプリケーションで行われるか、メモリの配置を制御したり、メモリを追跡したりするために行われます。ゲームは、特に特定のプラットフォーム/コンソールを対象とする場合、カスタムメモリマネージャーを頻繁に使用します。

これを行う1つの方法といくつかの理由についてのかなり良いブログ投稿があります。

于 2009-08-03T18:51:22.197 に答える