在c#中以字节为单位查找对象实例的大小

时间:2009-07-14 22:02:42

标签: c#

对于任意实例(不同对象,合成,单个对象等的集合)

如何确定其大小(以字节为单位)?

(我目前收集了各种对象,我正在尝试确定它的聚合大小)

编辑:是否有人为Object编写了可以执行此操作的扩展方法?那是非常整洁的。

16 个答案:

答案 0 :(得分:53)

首先,警告:接下来的内容严格地说是丑陋的,无证件的黑客攻击。不要依赖于这项工作 - 即使它现在适用于您,它可能会在明天停止工作,任何次要或主要的.NET更新。

您可以在CLR内部MSDN Magazine Issue 2005 May - Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects上使用本文中的信息 - 最后一次检查,它仍然适用。这是如何完成的(它通过类型的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。对于它们,您将把所有包含元素的大小添加到它们的基本实例大小。

答案 1 :(得分:19)

如果你正在处理可序列化的对象,你可以通过假装用二进制序列化器序列化(但将输出路由到遗忘)来近似大小。

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

答案 2 :(得分:7)

对于非托管类型,也就是值类型,结构:

        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读取的大小将比未对齐更大的影响。

答案 3 :(得分:5)

这不适用于当前的.NET实现,但要记住垃圾收集/托管运行时的一件事是对象的分配大小可以在程序的整个生命周期中发生变化。例如,一些世代垃圾收集器(例如Generational/Ulterior Reference Counting Hybrid collector)只需要在对象从托儿所移动到成熟空间后存储某些信息。

这使得无法创建可靠的通用API来公开对象大小。

答案 4 :(得分:4)

这在运行时是不可能的。

但是有各种内存分析器可以显示对象大小。

编辑:您可以编写第二个程序,使用CLR Profiling API对第一个程序进行概要分析,并通过远程处理或其他方式与之通信。

答案 5 :(得分:4)

安全解决方案,并进行了一些优化 CyberSaving/MemoryUsage code。 有些情况:

/* 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

答案 6 :(得分:3)

不直接回答问题,但对于那些有兴趣在调试时调查对象大小的人:

  1. 在 VS 中开始调试,确保显示“诊断”窗口
  2. 设置断点(可选)
  3. 暂停时点击“内存使用”中的“拍摄快照”
  4. 探索快照(可选择按字母顺序对对象列表进行排序以找到您感兴趣的类型)

enter image description here

答案 7 :(得分:2)

AFAIK,你不能,没有实际深入计算每个成员的大小(以字节为单位)。但是,成员的大小(如集合中的元素)是否会计入对象的大小,或指向该成员的指针是否计入对象的大小?取决于你如何定义它。

在我想根据消耗的内存限制缓存中的对象之前,我遇到过这种情况。

好吧,如果有一些技巧可以做到,我很高兴知道它!

答案 8 :(得分:2)

对于值类型,您可以使用Marshal.SizeOf。当然,它返回在非托管内存中编组结构所需的字节数,这不一定是CLR使用的内容。

答案 9 :(得分:2)

使用Son Of Strike,其中包含ObjSize命令。

请注意,实际占用的内存总是大于ObjSize报告,因为synkblk直接位于对象数据之前。

在此处详细了解MSDN Magazine Issue 2005 May - Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects

答案 10 :(得分:1)

您可以使用反射来收集所有公共成员或属性信息(给定对象的类型)。但是,如果没有遍历对象上的每个单独的数据,就无法确定大小。

答案 11 :(得分: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;
    }
}

不,这不会给你在内存中使用的确切大小。如前所述,这是不可能的。但它会给你一个粗略的估计。

请注意,这也很慢。

答案 12 :(得分:1)

从Pavel和jnm2:

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

请注意,因为它仅适用于连续的内存对象

答案 13 :(得分:1)

对于结构/值的数组,我得到以下不同的结果:

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

(过于简单的示例)

无论采用哪种方法,您实际上都需要了解.Net如何正确解释结果。 例如,返回的元素大小是“对齐的”元素大小,并带有一些填充。 开销以及大小的不同取决于类型的用法:在GC堆上,在堆栈上,在字段上,在数组元素上都是“装箱”。

(我想知道使用“虚拟”空结构(不带任何字段)来模仿泛型的“可选”参数会对内存产生什么影响;使用包含空结构的不同布局进行测试,我可以看到一个空struct每个元素至少使用1个字节;我隐约记得这是因为.Net需要为每个字段使用不同的地址,如果字段确实为空/ 0大小,则该地址将无法工作。

答案 14 :(得分:0)

最简单的方法是:int size = *((int*)type.TypeHandle.Value + 1)

我知道这是实现细节,但是GC依赖它,因此它必须尽可能接近方法表的开头,以提高效率,还要考虑到GC代码复杂性是谁将来都不会敢更改的。实际上,它适用于.net framework + .net核心的每个次要/主要版本。 (当前无法测试1.0)
如果需要更可靠的方法,请在动态程序集中使用[StructLayout(LayoutKind.Auto)]发出结构,并以相同的顺序完全相同的字段,并使用sizeof IL指令获取其大小。您可能希望在struct中发出一个静态方法,该方法仅返回此值。然后为对象标题添加2 * IntPtr.Size。这应该给您确切的价值。
但是,如果您的类是从另一个类派生的,则需要分别查找基类的每个大小,然后再次为标头添加它们+ 2 * Inptr.Size。您可以通过获取带有BindingFlags.DeclaredOnly标志的字段来实现此目的。
数组和字符串只是将其长度*元素大小相加。 对于累积对象的累积大小,您需要实施更复杂的解决方案,其中涉及访问每个字段并检查其内容。

答案 15 :(得分:0)

我已经为.NET中的不同集合创建了基准测试:https://github.com/scholtz/TestDotNetCollectionsMemoryAllocation

.NET Core 2.2具有1,000,000个对象并分配了3个属性的结果如下:

for value in d.values():
    for element in value:
        if 'current_value' in element:
            element['current_value'] = int(element['current_value'])

对于内存测试,我发现最适合使用

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