任意のインスタンス(異なるオブジェクトのコレクション、構成、単一のオブジェクトなど)
サイズをバイト単位で確認するにはどうすればよいですか?
(私は現在、さまざまなオブジェクトのコレクションを取得しており、その合計サイズを特定しようとしています)
編集:誰かがこれを行うことができるオブジェクトの拡張メソッドを書いたことがありますか?それはかなりきちんとしたイモでしょう。
任意のインスタンス(異なるオブジェクトのコレクション、構成、単一のオブジェクトなど)
サイズをバイト単位で確認するにはどうすればよいですか?
(私は現在、さまざまなオブジェクトのコレクションを取得しており、その合計サイズを特定しようとしています)
編集:誰かがこれを行うことができるオブジェクトの拡張メソッドを書いたことがありますか?それはかなりきちんとしたイモでしょう。
回答:
まず第一に、警告:以下は厳密には醜い、文書化されていないハックの領域です。この動作に依存しないでください。たとえ動作するようになったとしても、.NETのマイナーアップデートまたはメジャーアップデートにより、明日は動作しなくなる可能性があります。
あなたはCLR内部に、この記事の情報を使用することができますCLRはランタイムがオブジェクトの作成方法を参照してくださいするために、.NET Frameworkの内部構造にドリル- MSDNマガジン発行2005年5月 -最後に私がチェックし、それはまだ適用されました。これがどのように行われるかです(TypeHandleタイプの内部「基本インスタンスサイズ」フィールドを取得します)。
object obj = new List<int>(); // whatever you want to get the size of
RuntimeTypeHandle th = obj.GetType().TypeHandle;
int size = *(*(int**)&th + 1);
Console.WriteLine(size);
これは3.5 SP1 32ビットで動作します。64ビットでフィールドサイズが同じかどうかはわかりません。タイプやオフセットが異なる場合は、調整する必要があるかもしれません。
これは、すべてのインスタンスが同じ明確に定義されたタイプを持つすべての「通常の」タイプで機能します。これが当てはまらないのは確かに配列と文字列であり、私も信じていますStringBuilder。それらの場合は、含まれているすべての要素のサイズを基本インスタンスのサイズに追加する必要があります。
Cannot take the address of, get the size of, or declare a pointer to a managed type ('System.RuntimeTypeHandle')
Marshal.ReadInt32(type.TypeHandle.Value, 4)。x86およびx64で動作します。私は構造体とクラスの型のみをテストしました。これは値タイプのボックス化されたサイズを返すことに注意してください。@Pavel多分あなたはあなたの答えを更新できます。
typeとobj.GetType()彼の例では。どのフレームワークを使用しているかは問題ではなく、どのCLR(v2またはv4またはCoreCLR)かだけです。CoreCLRではこれを試していません。
シリアライズ可能なオブジェクトを使用している場合は、バイナリシリアライザーでシリアル化するふりをして(ただし、出力を忘却にルーティングする)、サイズを概算できる場合があります。
class Program
{
static void Main(string[] args)
{
A parent;
parent = new A(1, "Mike");
parent.AddChild("Greg");
parent.AddChild("Peter");
parent.AddChild("Bobby");
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf =
new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
SerializationSizer ss = new SerializationSizer();
bf.Serialize(ss, parent);
Console.WriteLine("Size of serialized object is {0}", ss.Length);
}
}
[Serializable()]
class A
{
int id;
string name;
List<B> children;
public A(int id, string name)
{
this.id = id;
this.name = name;
children = new List<B>();
}
public B AddChild(string name)
{
B newItem = new B(this, name);
children.Add(newItem);
return newItem;
}
}
[Serializable()]
class B
{
A parent;
string name;
public B(A parent, string name)
{
this.parent = parent;
this.name = name;
}
}
class SerializationSizer : System.IO.Stream
{
private int totalSize;
public override void Write(byte[] buffer, int offset, int count)
{
this.totalSize += count;
}
public override bool CanRead
{
get { return false; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush()
{
// Nothing to do
}
public override long Length
{
get { return totalSize; }
}
public override long Position
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
}
アンマネージ型、つまり値型の場合、構造体:
Marshal.SizeOf(object);
管理対象オブジェクトの場合、私が近づくと近似値になります。
long start_mem = GC.GetTotalMemory(true);
aclass[] array = new aclass[1000000];
for (int n = 0; n < 1000000; n++)
array[n] = new aclass();
double used_mem_median = (GC.GetTotalMemory(false) - start_mem)/1000000D;
シリアル化を使用しないでください。バイナリフォーマッタはヘッダーを追加するため、クラスを変更し、変更されたクラスに古いシリアル化されたファイルを読み込むことができます。
また、メモリの実際のサイズも通知されず、メモリの調整も考慮されません。
[編集]クラスのすべてのプロパティでBiteConverter.GetBytes(prop-value)を再帰的に使用すると、コンテンツをバイト単位で取得できます。これは、クラスまたは参照の重みを数えませんが、現実に非常に近くなります。データにバイト配列を使用し、アンマネージドプロキシクラスを使用して、サイズが重要な場合はポインターキャストを使用して値にアクセスすることをお勧めします。非整列メモリであるため、古いコンピューターでは遅くなりますが、現代のRAM上の巨大なデータセットはRAMから読み取るサイズを最小化することは、整列されていない場合よりも大きな影響を与えるため、かなり高速です。
これは現在の.NET実装には適用されませんが、ガベージコレクション/マネージランタイムについて留意すべき1つの点は、オブジェクトの割り当てサイズがプログラムの存続期間を通じて変更される可能性があることです。たとえば、一部の世代別ガベージコレクター(世代別/後方参照カウントハイブリッドコレクターなど)は、オブジェクトがナーサリから成熟したスペースに移動された後にのみ、特定の情報を格納する必要があります。
これにより、オブジェクトサイズを公開するための信頼できる汎用APIを作成することができなくなります。
いくつかの最適化による安全なソリューション CyberSaving / MemoryUsageコード。いくつかのケース:
/* test nullable type */
TestSize<int?>.SizeOf(null) //-> 4 B
/* test StringBuilder */
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) sb.Append("わたしわたしわたしわ");
TestSize<StringBuilder>.SizeOf(sb ) //-> 3132 B
/* test Simple array */
TestSize<int[]>.SizeOf(new int[100]); //-> 400 B
/* test Empty List<int>*/
var list = new List<int>();
TestSize<List<int>>.SizeOf(list); //-> 205 B
/* test List<int> with 100 items*/
for (int i = 0; i < 100; i++) list.Add(i);
TestSize<List<int>>.SizeOf(list); //-> 717 B
クラスでも動作します:
class twostring
{
public string a { get; set; }
public string b { get; set; }
}
TestSize<twostring>.SizeOf(new twostring() { a="0123456789", b="0123456789" } //-> 28 B
これは実行時に行うことは不可能です。
ただし、オブジェクトサイズを表示するさまざまなメモリプロファイラがあります。
編集:CLRプロファイリングAPIを使用して最初のプログラムのプロファイルを作成し、リモート処理などを介してそれと通信する2番目のプログラムを作成できます。
コマンドのあるSon Of Strikeを使用してくださいObjSize。
オブジェクトデータの直前にあるObjSizeため、消費される実際のメモリは常にレポートよりも大きいことに注意してくださいsynkblk。
両方の詳細については、こちらのMSDNマガジン発行2005 May-.NET Frameworkの内部にドリルインして、CLRがランタイムオブジェクトを作成する方法を参照してください。
私の知る限り、実際には、各メンバーのサイズをバイト単位で深くカウントすることなしにはできません。しかし、繰り返しになりますが、メンバー(コレクション内の要素など)のサイズはオブジェクトのサイズにカウントされますか、それともそのメンバーへのポインターはオブジェクトのサイズにカウントされますか?定義方法によって異なります。
私は、キャッシュ内のオブジェクトを、それらが消費するメモリに基づいて制限したい前に、この状況に遭遇しました。
まあ、それを行うためのいくつかのトリックがある場合、私はそれについて知ってうれしいです!
値タイプの場合、を使用できますMarshal.SizeOf。もちろん、アンマネージメモリ内の構造をマーシャリングするために必要なバイト数を返します。これは、CLRが使用するものとは限りません。
[Serializable]クラスを必要とせず、結果が正確な科学ではなく近似であるソリューションを探している人のために。私が見つけることができる最良の方法は、UTF32エンコーディングを使用してメモリストリームにJSONシリアル化することです。
private static long? GetSizeOfObjectInBytes(object item)
{
if (item == null) return 0;
try
{
// hackish solution to get an approximation of the size
var jsonSerializerSettings = new JsonSerializerSettings
{
DateFormatHandling = DateFormatHandling.IsoDateFormat,
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
MaxDepth = 10,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var formatter = new JsonMediaTypeFormatter { SerializerSettings = jsonSerializerSettings };
using (var stream = new MemoryStream()) {
formatter.WriteToStream(item.GetType(), item, stream, Encoding.UTF32);
return stream.Length / 4; // 32 bits per character = 4 bytes per character
}
}
catch (Exception)
{
return null;
}
}
いいえ、これはメモリで使用される正確なサイズを提供しません。前述のように、それは不可能です。しかし、それはあなたに大まかな見積もりを与えるでしょう。
これもかなり遅いことに注意してください。
Pavelとjnm2から:
private int DumpApproximateObjectSize(object toWeight)
{
return Marshal.ReadInt32(toWeight.GetType().TypeHandle.Value, 4);
}
隣接するメモリオブジェクトでのみ機能するため、注意してください。
.NETでさまざまなコレクションのベンチマークテストを作成しました:https : //github.com/scholtz/TestDotNetCollectionsMemoryAllocation
3つのプロパティが割り当てられた1,000,000のオブジェクトを持つ.NET Core 2.2の結果は次のとおりです。
Testing with string: 1234567
Hashtable<TestObject>: 184 672 704 B
Hashtable<TestObjectRef>: 136 668 560 B
Dictionary<int, TestObject>: 171 448 160 B
Dictionary<int, TestObjectRef>: 123 445 472 B
ConcurrentDictionary<int, TestObject>: 200 020 440 B
ConcurrentDictionary<int, TestObjectRef>: 152 026 208 B
HashSet<TestObject>: 149 893 216 B
HashSet<TestObjectRef>: 101 894 384 B
ConcurrentBag<TestObject>: 112 783 256 B
ConcurrentBag<TestObjectRef>: 64 777 632 B
Queue<TestObject>: 112 777 736 B
Queue<TestObjectRef>: 64 780 680 B
ConcurrentQueue<TestObject>: 112 784 136 B
ConcurrentQueue<TestObjectRef>: 64 783 536 B
ConcurrentStack<TestObject>: 128 005 072 B
ConcurrentStack<TestObjectRef>: 80 004 632 B
記憶テストのために、私は使用するのが最善だと思いました
GC.GetAllocatedBytesForCurrentThread()
構造体/値の配列の場合、私は異なる結果を出します:
first = Marshal.UnsafeAddrOfPinnedArrayElement(array, 0).ToInt64();
second = Marshal.UnsafeAddrOfPinnedArrayElement(array, 1).ToInt64();
arrayElementSize = second - first;
(簡略化された例)
どのようなアプローチでも、結果を正しく解釈するために.Netがどのように機能するかを理解する必要があります。たとえば、返される要素のサイズは、「アラインメントされた」要素のサイズで、パディングがいくつか含まれています。オーバーヘッドとサイズは、タイプの使用方法によって異なります。GCヒープの「ボックス化」、スタック、フィールド、配列要素のいずれかです。
(ジェネリックの「オプションの」引数を模倣するために「フィールド」なしで「ダミー」の空の構造体を使用することのメモリへの影響を知りたいと思っていました。空の構造体を含むさまざまなレイアウトでテストを行うと、空の構造体が(少なくとも)要素ごとに1バイト; .Netはフィールドごとに異なるアドレスを必要とするため、漠然と覚えています。
最も簡単な方法は次のとおりです。 int size = *((int*)type.TypeHandle.Value + 1)
私はこれが実装の詳細であることを知っていますが、GCはそれに依存しており、効率を上げるためにメソッドテーブルの最初にできるだけ近づける必要があります。実際、.net framework + .netコアのすべてのマイナー/メジャーバージョンで機能します。(現在、1.0をテストすることはできません)
より信頼できる方法が必要な場合は、動的アセンブリで[StructLayout(LayoutKind.Auto)]、同じ順序でまったく同じフィールドを持つ構造体を発行し、sizeof IL命令でそのサイズを取得します。この値を返すだけのstruct内で静的メソッドを発行したい場合があります。次に、オブジェクトヘッダーに2 * IntPtr.Sizeを追加します。これにより、正確な値が得られます。
ただし、クラスが別のクラスから派生している場合は、基本クラスの各サイズを個別に検索し、それらをヘッダーに再度追加する必要があります+ 2 * Inptr.Size。これを行うには、BindingFlags.DeclaredOnlyフラグ付きのフィールドを取得します。
配列と文字列は、その長さ*要素サイズをそのサイズに追加するだけです。集計オブジェクトの累積サイズについては、すべてのフィールドを訪問してその内容を検査することを含むより高度なソリューションを実装する必要があります。