11

私は最近、整数がメモリから4バイトを取ることを知りました。

最初にこのコードを実行し、メモリ使用量を測定しました。

int main()
{
   int *pointer;
}

ここに画像の説明を入力

  • 144KBかかりました。

次に、 1000 個の整数変数を割り当てるようにコードを修正しました。

int main()
{
   int *pointer;

   for (int n=0; n < 1000; n++)
     { 
       pointer = new int ; 
     }
}

ここに画像の説明を入力

  • 次に、 (168-144=) 24KB かかりまし
    たが、1000 個の整数が (4bytes x 1000=) 3.9KB を占めると想定されます

次に、1MB のメモリを消費する 262,144 個の整数変数を作成することにしました。

int main()
{
   int *pointer;

   for (int n=0; n < 262144; n++)
     { 
       pointer = new int ; 
     }
}

驚いたことに、今では 8MB かかります

ここに画像の説明を入力

メモリ使用量は、整数の数に応じて指数関数的に増加します。
なぜこうなった?

私は Kubuntu 13.04 (amd64)
を使用 しています。少し説明をお願いします。ありがとう!

注:sizeof(integer)リターン4

4

9 に答える 9

15

個別に割り当てられた動的オブジェクトのメモリは連続している必要はありません。new char[N]実際、 (通常は 16 である にアラインされる)のアラインメント要件により、標準のメモリ アロケータは16 バイト アラインされたメモリ以外alignof(std::maxalign_t)を返そうとしないかもしれません。したがって、各割り当ては実際には (少なくとも) 16 バイトを消費します。(さらに、アロケーターが内部簿記のためにさらにメモリーを必要とする場合があります。)int

もちろん、std::vector<int>(1000000)100 万個の動的な整数を適切に処理するために使用する必要があるという教訓があります。

于 2013-05-18T12:52:00.123 に答える
10

一般的なアロケーターでの最適化されていない割り当てには、いくらかのオーバーヘッドが伴います。INFO ブロックと STORAGE ブロックの 2 つの「ブロック」を考えることができます。情報ブロックは、おそらく STORAGE ブロックのすぐ前にあります。

したがって、割り当てると、メモリに次のようなものがあります。

        Memory that is actually accessible
        vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
--------------------------------------------
|  INFO |  STORAGE                         |
--------------------------------------------
^^^^^^^^^
Some informations on the size of the "STORAGE" chunk etc.

さらに、ブロックは特定の粒度 (int の場合は 16 バイトのようなもの) に沿って整列されます。

すぐにテストできるので、これが MSVC12 でどのように見えるかについて書きます。

私たちの記憶を見てみましょう。矢印は 16 バイト境界を示します。

int 割り当ての場合のメモリ レイアウト。 各ブロックは 4 バイトを表します

単一の 4 バイト整数を割り当てると、特定の 16 バイト境界 (2 番目の境界の後のオレンジ色の四角) で 4 バイトのメモリが得られます。このブロックの前にある 16 バイト (青いブロック) は、追加情報を格納するために占有されています。(エンディアンなどはここでは省略しますが、これがこの種のレイアウトに影響を与える可能性があることに注意してください。) 割り当てられたメモリの前にあるこの 16 バイト ブロックの最初の 4 バイトを読み取ると、割り当てられたメモリの数がわかります。バイト。

ここで 2 番目の 4 バイト整数 (緑色のボックス) を割り当てると、INFO ブロック (黄色/赤色) がその前に収まる必要があるため、その位置は 16 バイト境界の少なくとも 2 倍になりますが、右隣の境界ではそうではありません。 . 赤いブロックは、バイト数を含むブロックです。

簡単にわかるように、緑のブロックが 16 バイト前にあった場合、赤とオレンジのブロックがオーバーラップします。これは不可能です。

それは自分で確認できます。私はMSVC 2012を使用していますが、これはうまくいきました:

char * mem = new char[4096];
cout << "Number of allocated bytes for mem is: " << *(unsigned int*)(mem-16) << endl;
delete [] mem;


double * dmem = new double[4096];
cout << "Number of allocated bytes for dmem is: " << *(unsigned int*)(((char*)dmem)-16) << endl;
delete [] dmem;

版画

Number of allocated bytes for mem is: 4096
Number of allocated bytes for dmem is: 32768

そして、それは完全に正しいです。したがって、MSVC12 の場合、new を使用したメモリ割り当てには、サイズが少なくとも 16 バイトの追加の「INFO」ブロックがあります。

于 2013-05-18T13:21:42.263 に答える
4

複数の動的変数を割り当てています。各変数には 4 バイトのデータが含まれますが、メモリ マネージャーは通常、割り当てられたブロックに関する追加情報を格納し、各ブロックを整列する必要があるため、追加のオーバーヘッドが発生します。

試してみpointer = new int[262144];て、違いを見てください。

于 2013-05-18T12:52:35.330 に答える
2

を割り当てるたびにint:

  • メモリ アロケータは、メモリを任意の目的に使用できるように、一般にメモリ割り当てで適切なアライメントを提供する必要があるため、16 バイトでアライメントされたスペースのブロックを提供する必要があります。このため、各割り当ては通常、要求されたバイト数が少なくても、少なくとも 16 バイトを返します。(必要なアラインメントはシステムごとに異なる場合があります。また、小さな割り当てを最適化して使用するスペースを減らすことが考えられます。ただし、経験豊富なプログラマーは、小さな割り当てを何度も行うことを避けることを知っています。)
  • メモリ アロケータは、割り当てられたスペースの量を記憶するために、いくらかのメモリを使用する必要がありますfree。(これは、使用されるスペースの知識をオペレーターと組み合わせることによって最適化される可能性があります。ただし、一般的なメモリー割り当てルーチンは、通常、コンパイラーおよびコードdeleteから分離されています。)newdelete
  • メモリ アロケータは、データ構造にメモリを使用して、割り当ておよび解放されるメモリ ブロックに関する情報を整理する必要があります。おそらく、これらのデータ構造は O(n•log n) のスペースを必要とするため、小さな割り当てが多数あるとオーバーヘッドが大きくなります。

もう 1 つの考えられる影響は、メモリ使用量が増加するにつれて、アロケータがシステムからより大きなチャンクを要求して初期化することです。おそらく、メモリの初期プールを初めて使用するときに、アロケータはさらに 16 ページを要求します。次回は 32 を要求します。次回は 64 を要求します。メモリ アロケータがシステムに要求したメモリのうち、intオブジェクトに対する要求を満たすために実際に使用されたメモリの量はわかりません。

多くの小さなオブジェクトを動的に割り当てないでください。代わりに配列を使用してください。

于 2013-05-18T13:15:12.340 に答える
1

各宣言は、間にスペースを必要とするコンパイラのアラインメント オプションに適した新しい変数を作成します (変数の開始アドレスは 128 または 64 または 32 (ビット) の倍数である必要があり、これにより、多くの変数と 1 つの配列のメモリの効率が低下します)。 )。配列は、連続した領域を持つ方がはるかに便利です。

于 2013-05-18T12:57:39.337 に答える
1

他の質問で言及されているアラインメントとオーバーヘッドの問題に加えて、C++ ランタイム要求が OS からのメモリ割り当てを処理する方法が原因である可能性があります。

プロセスのデータ セクションがいっぱいになると、ランタイムはプロセスに割り当てるメモリを増やす必要があります。同じサイズのチャンクでこれを行うとは限りません。考えられる戦略は、メモリを要求するたびに、要求する量を増やすことです (おそらく毎回ヒープ サイズを 2 倍にする)。この戦略では、メモリをあまり使用しないプログラムのメモリ割り当てを小さく保ちますが、大規模なアプリケーションが新しい割り当てを要求する回数を減らします。

の下でプログラムを実行してstraceへの呼び出しを探し、brk毎回要求がどれだけ大きいかを確認してください。

于 2013-05-18T13:32:25.167 に答える
1

コンパイラが出力プログラムを作成する方法に依存すると思います。

プログラムのメモリ使用量には、プログラムのすべてのセクション (プログラムのアセンブリ ディレクティブを含む .text など) が含まれるため、ロード時にメモリの一部が消費されます。

また、より多くの変数の場合、メモリを割り当てるときにメモリが実際には連続していないため (メモリアライメント)、必要と思われるよりも多くのメモリが必要になる可能性があります。

于 2013-05-18T12:54:11.043 に答える
1

2 つの説明:

  1. (ヒープ上での) 動的メモリ割り当ては、必ずしも連続しているわけではありません。ご利用の際newはダイナミックアロケーションを行います。
  2. デバッグ シンボル (-gコンパイラ フラグ) を含めると、メモリ使用量が予想よりも大きくなる可能性があります。
于 2013-05-18T12:54:34.963 に答える
1

int だけでなく、オーバーヘッドのあるヒープ ブロックも割り当てています (プラットフォームによって異なります)。ヒープ情報を追跡する必要があります。代わりに int の配列を割り当てた場合、メモリ使用量が期待どおりになることがわかります。

于 2013-05-18T13:21:21.707 に答える