2013-04-08 3 views
8

最近のVisual Studio C++コンパイラを使用しているWindowsでC++を特に考えると、ヒープ実装に関する疑問があります。ヒープメモリ割り当てに関連するメモリオーバーヘッド(ヒープ内のマーカーなど)はありますか?

私はリリースコンパイラを使用していると仮定し、メモリの断片化/ヒープ上にメモリを割り当てることに伴うメモリオーバーヘッドはありますか?もしそうなら、割り当てあたりのバイト数はおよそ何バイトですか? 64ビットコードでは32ビットよりも大きいでしょうか?

現代のヒープ実装についてはよく分かりませんが、割り当てごとにヒープにマーカーが書き込まれているのか、何らかの種類のテーブル(ファイル割り当てテーブルなど)が維持されているかどうかは疑問です。

(私は主に 'マップ'のような標準ライブラリの機能について考えているので)ヒープを最適化するためにMicrosoft標準ライブラリの実装では独自のアロケータ(ツリーノードなど)を使用していますか使用法?

+2

通常、配列を割り当てるとき、いくつかのバイトが最初に格納され、将来の削除のために割り当てられたサイズが割り当てられます。大規模な割り当ての場合、オーバーヘッドはnoneに近くなります。一度に4バイトを割り当てると、メモリを2倍にすることができます。 –

答えて

7

はい、絶対に。

割り当てられたすべてのメモリブロックには、小さな可変部分(通常は最後)と同様に、「ヘッダー」の一定のオーバーヘッドがあります。正確にどれくらいの量が使用される正確なCランタイムライブラリに依存します。これまでは、割り当てごとに約32〜64バイトであることが実験的に分かっていました。可変部分はアラインメントに対処することです - 各メモリブロックは2^nベースアドレス(通常は8または16バイト)に整列されます。

私はstd::mapなどの内部設計の仕組みに精通していませんが、私はそこに特別な最適化があることを非常に疑っています。

あなたは非常に簡単でオーバーヘッドをテストすることができます。おそらく、ここの常連のほとんどである、pedantsに注意してください[

char *a, *b; 

a = new char; 
b = new char; 

ptrdiff_t diff = a - b; 

cout << "a=" << a << " b=" << b << " diff=" << diff; 

、上記ABの式は、1枚のアドレスを減算するので、未定義の動作を起動します割り当てられたアドレスと別のアドレスが未定義の動作です。これは、線形メモリアドレスを持たない機械に対処するためである。 「異なるタイプのデータは、そのタイプに基づいて場所に格納されます」。上記のことは、ヒープ用に複数のデータセグメントを持つセグメント化されたメモリモデルを使用しないx86ベースのOS(つまり、32ビットと64ビットモードでWindowsとLinuxで確実に動作すること)で確実に動作するはずです。

あなたは様々なタイプでそれを実行することをお勧めします - ちょうどdiffがである「タイプの数なので、あなたがそれを作る場合はになります 『』 4つのバイト単位ことを心に留めてあなたがreinterpret_cast<char*>(a) - reinterpret_cast<char *>(b);

を作ることができます。

[diffは負であってもよく、あなたが(abを削除せずに)ループでこれを実行する場合は、メモリの大部分が排出される突然のジャンプを見つけること、およびランタイムライブラリは別の大きなブロックを割り当て]

+1

技術的には、「a-b」はUBです。 –

+0

その旨のコメントを追加しました。 2つの独立した割り当ての間の距離を判断するためのより良い方法を提案している場合は、お気軽にお立ち寄りください。 –

+0

いいえ、私はそれを暗示していませんでした。このようなことを証明するために、ときにはあなたはUBに頼っています。問題はありません:) –

4

Visual C++では、割り当てられたバッファの境界付近に制御情報(リンク/サイズ、場合によってはチェックサム)が埋め込まれています。メモリの割り当てや割り当て解除中にバッファオーバーフローが発生する。あなたはmalloc()ニーズが適切にすべての基本型(charintlong longdoublevoid*void(*)())と、そのアライメントのために整列のポインタを返すようにということを覚えておいてくださいその上で

ので、一般的に最大のタイプの大きさでありますそれは8または16バイトであってもよい。 1バイトを割り当てると、7〜15バイトはアライメントだけに失われます。私はoperator newに同じ動作があるかどうかは分かりませんが、大した場合もあります。

これはあなたに考えを与えるはずです。精密なメモリの浪費は、ドキュメント(存在する場合)またはテストからのみ決定できます。言語規格は、それをいかなる意味でも定義していない。

2

はい。すべての実用的なダイナミックメモリアロケータは最小値粒度 です。たとえば、粒度が16バイトで、1バイトしか要求しない場合、16バイト全体が割り当てられます。 17バイトを要求すると、サイズが32バイトのブロックが割り当てられます。

アライメントの(関連する)問題もあります。

かなりの数のアロケータは、サイズマップとフリーリストの組み合わせのように見える - 彼らは「バケット」に潜在的な割り当てサイズを分割し、それぞれに別々のフリーリストを保持します。 Doug Lea's mallocをご覧ください。そこさまざまなトレードオフを持つ他の多くの割り当て技術があるが、それは


通常8または16バイト...ここに範囲を超えています。アロケータが空きリストを使用する場合、空きスロットは8バイト(32ビット)または16バイト(16ビット)より小さくすることができないため、フリー・スロット内に2つのポインタをエンコードする必要があります。例えば、アロケータが4バイトの要求を満たすために8バイトのスロットを分割しようとすると、残りの4バイトは空きリストポインタを符号化するのに十分な余裕がない。例えば

ご使用のプラットフォーム上long longが8バイトの場合アロケータの内部データ構造がより小さなブロックを処理することができたとしても、その後、実際に小さなブロックを割り当てることは次の8バイトの割り当てを押すかもしれませんアラインされていないメモリアドレスに転送します。

+0

情報とmallocのリンクありがとう、@ Branko、それはヒープ管理設計の非常に良い基本的な導入であるようです。 –

関連する問題