web-dev-qa-db-ja.com

グローバルなnewをオーバーロードして削除する理由はありますか?

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

追加
オーバーロードされた削除関数にバグが見つかりました-メモリが常に解放されるとは限りませんでした。そして、それはそれほどメモリクリティカルではないアプリケーションでした。また、これらの過負荷を無効にすると、パフォーマンスが約0.5%だけ低下します。

53
MadH

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

  • poolingすべての小さな割り当て-オーバーヘッドを減らし、断片化を減らし、小さな割り当ての多いアプリのパフォーマンスを向上させることができます
  • フレーミング既知のライフタイムの割り当て-この期間の最後まですべての解放を無視し、次にそれらすべてを一緒に解放します(確かにこれをもっと行いますグローバルよりもローカル演算子のオーバーロードがある)
  • 配置調整-キャッシュライン境界など
  • alloc fill-初期化されていない変数の使用法を明らかにするのに役立ちます
  • free fill-以前に削除されたメモリの使用状況を明らかにするのに役立ちます
  • delayed free-フリーフィルの効果を高め、パフォーマンスを向上させることがあります
  • sentinelsまたはfenceposts-バッファオーバーラン、アンダーラン、および時折ワイルドポインター
  • リダイレクト割り当て-NUMA、特別なメモリ領域を考慮したり、メモリ内で別々のシステムを分離したりするために(たとえば、組み込みスクリプト言語やDSLの場合)
  • ガベージコレクションまたはクリーンアップ-これらの埋め込みスクリプト言語にも役立ちます
  • ヒープ検証-N個の割り当て/解放ごとにヒープデータ構造をウォークスルーして、すべてが正常に見えることを確認できます
  • アカウンティングリークトラッキングおよび使用状況のスナップショット/統計(スタック、割り当て年齢など)

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

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

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

C/C++用に存在するアロケーターとデバッグシステムのいくつかを見てください。これらのアイデアやその他のアイデアがすぐに思い浮かびます。

(古くて独創的な本の1つは Writing Solid Code です。これは、Cでカスタムアロケーターを提供する理由の多くを説明していますが、そのほとんどは依然として非常に関連性があります。)

明らかに、これらの優れたツールのいずれかを使用できる場合は、独自のツールを使用するのではなく、使用することをお勧めします。

それがより速く、より簡単で、ビジネス/法的な煩わしさが少なく、プラットフォームでまだ何も利用できない、または単にもっと有益な状況があります:掘り下げてグローバルな過負荷を書いてください。

76
leander

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

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

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

26
GManNickG

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

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

固定ブロックアロケーターは、ヒープからウィリーニリーを割り当てるよりもはるかに高速で断片化の傾向が少ないため、これにより、アドレス空間全体にうんちをするのではなく、くだらない3Dパーティコードをプールから強制的に割り当てることができます。

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

15
Crashworks

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

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

10
Sam Harwell

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

6
cwap

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

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

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

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

4
Chris Masterton

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

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

3
Edouard A.

ランダムなクラッシュ以外の何かによってメモリ不足の状態に応答できるようにすることは、アプリケーションにとって素晴らしいトリックになる可能性があります。これを行うには、newをデフォルトのnewの単純なプロキシにして、障害をキャッチし、いくつかのものを解放して再試行します。

最も簡単な手法は、その目的のために、起動時にメモリの空白ブロックを予約することです。また、利用できるキャッシュがある場合もあります。考え方は同じです。

最初の割り当ての失敗が発生した場合でも、メモリ不足の状態についてユーザーに警告する時間があります(「もう少し長く生き残ることはできますが、作業内容を保存して他のアプリケーションを閉じたい場合があります」)。状態をディスクに保存するか、サバイバルモードに切り替えるか、またはコンテキストで意味のあるものなら何でも。

2
Nicolas Simonet

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

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

2
Dan Olson

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

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

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

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

2
macbutch

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

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

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

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

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

2
Juan

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

2
Ben Lings

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

ただし、99%の時間は、メモリが割り当てられて解放される頻度、場所、タイミングをログに記録するデバッグ機能として実行されます。

2
Martin Beckett

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

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

0
Tobias

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

これはかなり良いです これを行う1つの方法についてのブログ投稿 そしていくつかの理由。

0
Runeborg

オーバーロードされたnew演算子を使用すると、プログラマーはプログラムから余分なパフォーマンスを引き出すこともできます。たとえば、クラスでは、新しいノードの割り当てを高速化するために、削除されたノードのリストが維持され、新しいノードが割り当てられたときにそれらのメモリを再利用できるようになります。この場合、オーバーロードされた削除演算子はノードをリストに追加します。削除されたノードとオーバーロードされた新しい演算子の数は、メモリ割り当てを高速化するために、ヒープからではなく、このリストからメモリを割り当てます。削除されたノードのリストが空の場合、ヒープのメモリを使用できます。

0
Coding beginner