【问题标题】:Find size of object instance in bytes in c#在 C# 中查找对象实例的大小(以字节为单位)
【发布时间】:2010-11-10 19:53:34
【问题描述】:

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

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

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

编辑:有人为 Object 编写了一个扩展方法可以做到这一点吗?这将是相当整洁的 imo。

【问题讨论】:

标签: c#


【解决方案1】:

这在运行时是不可能做到的。

不过,有多种内存分析器可以显示对象大小。

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

【讨论】:

  • 如果无法在运行时完成,内存分析器如何提供信息?
  • 使用 Profiling API。但是,程序无法分析自身
  • 有趣。如果我想让代码处理对象消耗过多内存的情况怎么办?
  • 那么你会处理具有自我意识的软件,我会非常害怕。 :-) 说真的,“单一责任主体”——让程序成为程序,让其他一些代码监视占用过多内存的对象。
  • @Janie:您还会对尺寸的重要性及其与性能的关系做出假设。我认为在你这样做之前,你会想成为一个真正的低级 CLR 性能专家(那种已经知道 Profiling API 的人)。否则,您可能会将之前的经验应用于不适用的情况。
【解决方案2】:

AFAIK,如果不深入计算每个成员的大小(以字节为单位),您就无法做到。但同样,成员的大小(如集合中的元素)是否计入对象的大小,或者指向该成员的指针是否计入对象的大小?取决于你如何定义它。

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

好吧,如果有什么技巧可以做到这一点,我很高兴知道!

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      这使得创建可靠的通用 API 来公开对象大小变得不可能。

      【讨论】:

      • 有趣。那么人们如何动态确定他们的对象/对象集合的大小呢?
      • 这取决于他们需要什么。如果对于 P/Invoke(本机代码互操作),它们使用 Marshal.SizeOf(typeof(T))。如果用于内存分析,他们使用与执行环境合作的单独分析器来提供信息。如果您对数组中的元素对齐感兴趣,可以在 DynamicMethod 中使用 SizeOf IL 操作码(我认为 .NET 框架中没有更简单的方法)。
      【解决方案5】:

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

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

      【讨论】:

      • 当然,这可以为您提供最小大小,但不会告诉您有关内存大小的任何信息。
      • 大声笑,我回来检查回复之前的下一个灯泡是使用二进制序列化程序。约翰,这怎么不会给你内存中的实际大小?
      • 它会给你序列化的大小,这将是序列化程序想要的大小,用于“序列化程序”的目的。这些可能与“坐在记忆中”的目的不同。例如,序列化程序可能会将较小的整数存储在三个字节中。
      • 就像我说的,这只是一个近似值。它并不完美,但我不同意它告诉你关于内存大小的“任何内容”。我想说它给了你一些的想法——更大的序列化通常与更大的内存大小相关。存在某种关系。
      • 我同意 - 大致估计 .NET 对象图的大小很有用。
      【解决方案6】:

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

      【讨论】:

      【解决方案7】:

      首先,警告:以下内容仅限于丑陋、无证的黑客行为。不要依赖这个工作 - 即使它现在对你有用,它可能会在明天停止工作,任何次要或主要的 .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。对于他们,您需要将所有包含元素的大小添加到它们的基本实例大小中。

      【讨论】:

      • 没有。没有“正确”的方法可以做到这一点,因为这不是一个表现良好的 .NET 应用程序首先应该关注的事情。上述内容直接与 CLR 特定实现的内部数据结构相混淆(例如,在 .NET 的下一版本中可能很容易更改)。
      • 这应该在 C# 中工作还是只在托管 c++ 中工作?到目前为止,我对 C# 的尝试并不满意:Cannot take the address of, get the size of, or declare a pointer to a managed type ('System.RuntimeTypeHandle')
      • .NET 4 版本甚至不需要不安全代码:Marshal.ReadInt32(type.TypeHandle.Value, 4) 适用于 x86 和 x64。我只测试了结构和类类型。请记住,这会返回值类型的 boxed 大小。 @Pavel 也许你可以更新你的答案。
      • @sab669 好吧,在他的示例中将type 替换为obj.GetType()。使用哪个框架并不重要,只需要 CLR(v2 或 v4 或 CoreCLR)。我还没有在 CoreCLR 上尝试过。
      • @SamGoldberg 手动计算需要一百万个边缘情况下的大量工作。 Sizeof 告诉您对象的静态大小,而不是对象运行时图的内存消耗。 VS2017 的内存和 CPU 配置文件非常好,ReSharper 和其他工具也是如此,这就是我用来衡量的。
      【解决方案8】:

      使用Son Of Strike,它有一个命令ObjSize。

      请注意,实际消耗的内存始终大于ObjSize 报告,因为synkblk 直接位于对象数据之前。

      在这里MSDN Magazine Issue 2005 May - Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects了解更多信息。

      【讨论】:

        【解决方案9】:

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

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

        【讨论】:

          【解决方案10】:

          安全解决方案,进行了一些优化 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
          

          【讨论】:

          • 这也是我会采用的方法。您可以在图中添加一组以前遇到的对象,以避免 a) 无限递归和 b) 避免两次添加相同的内存。
          【解决方案11】:

          对于寻求不需要[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】:

            来自 Pavel 和 jnm2:

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

            请注意,它只适用于连续的内存对象

            【讨论】:

              【解决方案13】:

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

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

              【讨论】:

                【解决方案14】:

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

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

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

                【讨论】:

                  【解决方案15】:

                  对于结构/值的数组,我有不同的结果:

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

                  (过于简单的例子)

                  无论采用何种方法,您确实需要了解 .Net 如何正确解释结果。 例如,返回的元素大小是“对齐的”元素大小,带有一些填充。 开销和大小因使用类型而异:在 GC 堆上、堆栈上、作为字段、作为数组元素“装箱”。

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

                  【讨论】:

                    【解决方案16】:

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

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

                    【讨论】:

                      猜你喜欢
                      • 2014-01-27
                      • 1970-01-01
                      • 2017-12-18
                      • 1970-01-01
                      • 2016-04-08
                      • 1970-01-01
                      • 2013-05-03
                      • 1970-01-01
                      相关资源
                      最近更新 更多