对于任意实例(包括不同对象的集合,组合,单个对象等),如何确定其占用的字节数?
(我目前有一个包含各种对象的集合,并尝试确定它们的聚合大小)
编辑:是否有人编写了一种可为Object编写的扩展方法来完成此操作?我认为那会很不错。
对于任意实例(包括不同对象的集合,组合,单个对象等),如何确定其占用的字节数?
(我目前有一个包含各种对象的集合,并尝试确定它们的聚合大小)
编辑:是否有人编写了一种可为Object编写的扩展方法来完成此操作?我认为那会很不错。
首先,警告一下:接下来的内容严格属于丑陋、未记录的黑科技领域。不要依赖这个方法的可行性——即使现在它能用,明天随着任何微小或重大的.NET更新,都有可能无法使用。
您可以使用本文介绍的CLR内部信息《MSDN杂志》2005年5月刊 - 深入探究.NET框架内部以了解CLR如何创建运行时对象 - 我上次检查时,它仍然适用。以下是此操作的实现方式(通过类型的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
也是如此。
无法获取托管类型('System.RuntimeTypeHandle')的地址、大小或声明指针。
- MaslowMarshal.ReadInt32(type.TypeHandle.Value, 4)
对于 x86 和 x64 都适用。我只测试了结构体和类类型。请记住,这将返回值类型的 装箱 大小。@Pavel也许你可以更新你的回答。 - jnm2obj.GetType()
替换 type
。无论你使用哪个框架,只要看 CLR(v2 或 v4 或 CoreCLR)。我还没有在 CoreCLR 上尝试过这个。 - jnm2如果您正在处理可序列化的对象,可以尝试使用二进制序列化器模拟将其序列化(但将输出路由到虚无)的方式来近似估算大小。
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;
请不要使用序列化。二进制格式添加了头部,因此您可以更改类并将旧的序列化文件加载到修改后的类中。
此外,它不会告诉您在内存中的实际大小,也不会考虑内存对齐。
[编辑] 通过对类的每个属性递归地使用BitConverter.GetBytes(prop-value),您将获得以字节为单位的内容,这不计算类或引用的权重,但更接近现实。 如果大小很重要,我建议使用字节数组来存储数据,并使用非托管代理类使用指针转换访问值,注意这将是非对齐的内存,因此在旧计算机上速度会很慢,但在现代RAM上处理巨大数据集时将快得多,因为将从RAM读取的大小最小化将产生更大的影响。
安全的解决方案,同时进行了一些优化 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
如果有人正在寻找一种不需要使用[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;
}
}
不,这不会给出在内存中使用的精确大小。正如先前提到的那样,这是不可能的。但它会给你一个大概的估计。
请注意,这也相当慢。
ObjSize
。请注意,由于一个synkblk
直接位于对象数据之前,实际内存消耗总是大于ObjSize
报告的大小。在此处阅读更多信息:MSDN Magazine Issue 2005 May - Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects。对于结构体/值的数组,我使用以下方法得到不同的结果:
first = Marshal.UnsafeAddrOfPinnedArrayElement(array, 0).ToInt64();
second = Marshal.UnsafeAddrOfPinnedArrayElement(array, 1).ToInt64();
arrayElementSize = second - first;
< p >(简化的例子)
无论采用何种方法,您都需要了解 .Net 的工作原理才能正确解释结果。例如,返回的元素大小是“对齐”元素大小,并带有一些填充。根据类型的使用情况,“包装”在 GC 堆上,堆栈上,作为字段或作为数组元素时,开销和大小是不同的。
(我想知道使用“虚拟”空结构体(没有任何字段)来模拟泛型的“可选”参数会产生什么记忆影响;通过使用涉及空结构体的不同布局进行测试,我可以看到每个空结构体使用(至少)1字节的内存;我模糊地记得这是因为 .Net 需要为每个字段提供不同的地址,如果字段真的为空/大小为0,则无法正常工作)。