オブジェクトインスタンスのサイズをc#でバイト単位で見つける


113

任意のインスタンス(異なるオブジェクトのコレクション、構成、単一のオブジェクトなど)

サイズをバイト単位で確認するにはどうすればよいですか?

(私は現在、さまざまなオブジェクトのコレクションを取得しており、その合計サイズを特定しようとしています)

編集:誰かがこれを行うことができるオブジェクトの拡張メソッドを書いたことがありますか?それはかなりきちんとしたイモでしょう。



回答:


60

まず第一に、警告:以下は厳密には醜い、文書化されていないハックの領域です。この動作に依存しないでください。たとえ動作するようになったとしても、.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。それらの場合は、含まれているすべての要素のサイズを基本インスタンスのサイズに追加する必要があります。


いいえ。これは、正常に動作する.NETアプリケーションが最初に考慮する必要があるものではないため、これを行う「適切な」方法はありません。上記は、CLRの特定の実装内部データ構造(たとえば、次のバージョンの.NETで簡単に変更される可能性があります)と直接つながっています。
Pavel Minaev 2010年

3
これはC#で動作するはずですか、それともマネージドC ++のみですか?それはこれまでのところ、私はそれを試してみたことをC#ではない幸せです:Cannot take the address of, get the size of, or declare a pointer to a managed type ('System.RuntimeTypeHandle')
マズロー

17
これの.NET 4バージョンでは、安全でないコードも必要ありませんMarshal.ReadInt32(type.TypeHandle.Value, 4)。x86およびx64で動作します。私は構造体とクラスの型のみをテストしました。これは値タイプのボックス化されたサイズを返すことに注意してください。@Pavel多分あなたはあなたの答えを更新できます。
jnm2 2015年

2
@ sab669も、交換してくださいtypeobj.GetType()彼の例では。どのフレームワークを使用しているかは問題ではなく、どのCLR(v2またはv4またはCoreCLR)かだけです。CoreCLRではこれを試していません。
jnm2 2017

2
@SamGoldberg手動でこれを計算することは、100万のエッジケースで多くの作業になります。Sizeofは、オブジェクトの実行時グラフのメモリ消費ではなく、オブジェクトの静的サイズを示します。VS2017のメモリとCPUプロファイリングは非常に優れており、ReSharperや他のツールも同様です。これを測定に使用します。
jnm2 2017年

20

シリアライズ可能なオブジェクトを使用している場合は、バイナリシリアライザーでシリアル化するふりをして(ただし、出力を忘却にルーティングする)、サイズを概算できる場合があります。

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();
    }
}

6
もちろん、これにより最小サイズを取得できますが、メモリ内のサイズについては何もわかりません。
John Saunders、

Lol、返信を確認するために戻る前に私が持っていた次の電球は、バイナリシリアライザーを使用していました。ジョン、どうしてこれがメモリの実際のサイズにならないのですか?
Janie、

2
「シリアライザ」の目的で、シリアライザが必要とするサイズになるシリアライズされたサイズが得られます。それらは、「シットインメモリ」の目的とはおそらく異なります。たぶん、シリアライザは、たとえば3バイトの小さな整数を格納します。
John Saunders、

4
私が言ったように、それは単なる近似です。それは完璧ではありませんが、メモリ内のサイズについて「何もない」と言うことには同意しません。私はそれがあなたにいくつかの考えを与えたと言います-より大きなシリアライゼーションは一般により大きなメモリ内サイズと相関しているでしょう。いくつかの関係があります。
BlueMonkMN 2009

同意します-.NETオブジェクトグラフのサイズの大まかな見積もりを取得すると便利です。
Craig Shearer、2011年

8

アンマネージ型、つまり値型の場合、構造体:

        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から読み取るサイズを最小化することは、整列されていない場合よりも大きな影響を与えるため、かなり高速です。


5

これは現在の.NET実装には適用されませんが、ガベージコレクション/マネージランタイムについて留意すべき1つの点は、オブジェクトの割り当てサイズがプログラムの存続期間を通じて変更される可能性があることです。たとえば、一部の世代別ガベージコレクター(世代別/後方参照カウントハイブリッドコレクターなど)は、オブジェクトがナーサリから成熟したスペースに移動された後にのみ、特定の情報を格納する必要があります。

これにより、オブジェクトサイズを公開するための信頼できる汎用APIを作成することができなくなります。


面白い。では、オブジェクト/オブジェクトのコレクションのサイズを動的に決定するにはどうすればよいでしょうか。
Janie、

2
それは彼らが何のためにそれを必要とするかによる。P / Invoke(ネイティブコードの相互運用)の場合、Marshal.SizeOf(typeof(T))を使用します。メモリプロファイリングの場合は、実行環境と連携して情報を提供する別のプロファイラーを使用します。配列内の要素の配置に関心がある場合は、DynamicMethodでSizeOf ILオペコードを使用できます(これを行うための.NETフレームワークには、もっと簡単な方法はないと思います)。
サム・ハーウェル

5

いくつかの最適化による安全なソリューション Cyber​​Saving / 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

これも私のアプローチです。以前に遭遇したオブジェクトのセットをグラフに追加して、a)無限再帰を回避し、b)同じメモリを2回追加することを回避できます。
mafu

4

これは実行時に行うことは不可能です。

ただし、オブジェクトサイズを表示するさまざまなメモリプロファイラがあります。

編集CLRプロファイリングAPIを使用して最初のプログラムのプロファイルを作成し、リモート処理などを介してそれと通信する2番目のプログラムを作成できます。


17
実行時に実行することが不可能な場合、メモリプロファイラーはどのように情報を提供しますか?
ジャニー

2
プロファイリングAPIを使用する。ただし、プログラム自体をプロファイルすることはできません
SLaks 2009

面白い。オブジェクトが大量のメモリを消費している場合にコードで対処したい場合はどうなりますか?
ジャニー

4
それからあなたは自己認識ソフトウェアを扱うでしょう、そして私は非常に恐れています。:-)真剣に、「単一の責任の原則」-プログラムをプログラムにして、他のいくつかのコードに、メモリを大量に消費しているオブジェクトを監視させます。
ジョンサンダース、

2
@Janie:サイズの重要性とそれがパフォーマンスにどのように関連するかについても想定します。その前に、実際の低レベルのCLRパフォーマンスのエキスパート(プロファイリングAPIについて既に知っている種類)になりたいと思います。それ以外の場合は、以前の経験を、それらが適用されない状況に適用している可能性があります。
John Saunders、


2

私の知る限り、実際には、各メンバーのサイズをバイト単位で深くカウントすることなしにはできません。しかし、繰り返しになりますが、メンバー(コレクション内の要素など)のサイズはオブジェクトのサイズにカウントされますか、それともそのメンバーへのポインターはオブジェクトのサイズにカウントされますか?定義方法によって異なります。

私は、キャッシュ内のオブジェクトを、それらが消費するメモリに基づいて制限したい前に、この状況に遭遇しました。

まあ、それを行うためのいくつかのトリックがある場合、私はそれについて知ってうれしいです!


2

値タイプの場合、を使用できますMarshal.SizeOf。もちろん、アンマネージメモリ内の構造をマーシャリングするために必要なバイト数を返します。これは、CLRが使用するものとは限りません。


SizeOf(Object)は、将来のリリースで使用できなくなる可能性があります。代わりに、SizeOf <T>()を使用してください。詳細については、go.microsoft.com
fwlink

1

リフレクションを使用して、すべてのパブリックメンバーまたはプロパティ情報(オブジェクトのタイプを指定)を収集できます。ただし、オブジェクトの個々のデータを調べずにサイズを決定する方法はありません。


1

[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;
    }
}

いいえ、これはメモリで使用される正確なサイズを提供しません。前述のように、それは不可能です。しかし、それはあなたに大まかな見積もりを与えるでしょう。

これもかなり遅いことに注意してください。


1

Pavelとjnm2から:

private int DumpApproximateObjectSize(object toWeight)
{
   return Marshal.ReadInt32(toWeight.GetType().TypeHandle.Value, 4);
}

隣接するメモリオブジェクトでのみ機能するため、注意してください。


1

.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()

1

構造体/値の配列の場合、私は異なる結果を出します:

first = Marshal.UnsafeAddrOfPinnedArrayElement(array, 0).ToInt64();
second = Marshal.UnsafeAddrOfPinnedArrayElement(array, 1).ToInt64();
arrayElementSize = second - first;

(簡略化された例)

どのようなアプローチでも、結果を正しく解釈するために.Netがどのように機能するかを理解する必要があります。たとえば、返される要素のサイズは、「アラインメントされた」要素のサイズで、パディングがいくつか含まれています。オーバーヘッドとサイズは、タイプの使用方法によって異なります。GCヒープの「ボックス化」、スタック、フィールド、配列要素のいずれかです。

(ジェネリックの「オプションの」引数を模倣するために「フィールド」なしで「ダミー」の空の構造体を使用することのメモリへの影響を知りたいと思っていました。空の構造体を含むさまざまなレイアウトでテストを行うと、空の構造体が(少なくとも)要素ごとに1バイト; .Netはフィールドごとに異なるアドレスを必要とするため、漠然と覚えています。


0

最も簡単な方法は次のとおりです。 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フラグ付きのフィールドを取得します。
配列と文字列は、その長さ*要素サイズをそのサイズに追加するだけです。集計オブジェクトの累積サイズについては、すべてのフィールドを訪問してその内容を検査することを含むより高度なソリューションを実装する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.