对于任何任意实例(不同对象,组成,单个对象等的集合)
如何确定其大小(以字节为单位)?
(我目前有各种对象的集合,我正在尝试确定它的总大小)
编辑:有人编写了可以做到这一点的对象扩展方法吗?那真是整洁的imo。
对于任何任意实例(不同对象,组成,单个对象等的集合)
如何确定其大小(以字节为单位)?
(我目前有各种对象的集合,我正在尝试确定它的总大小)
编辑:有人编写了可以做到这一点的对象扩展方法吗?那真是整洁的imo。
Answers:
首先,要发出警告:接下来的事情完全是在丑陋的,未经记录的黑客领域。不要依赖于此工作-即使现在为您工作,它也可能会因任何次要或主要的.NET更新而在明天停止工作。
您可以在CLR内部构件上使用本文中的信息,MSDN杂志2005年5月-深入.NET Framework内部构件以查看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。对于它们,您需要将所有包含的元素的大小添加到其基本实例大小中。
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),您将获得以字节为单位的内容,该内容不计算类或引用的权重,但更接近实际情况。如果大小很重要,我建议使用字节数组存储数据,并使用非托管代理类通过指针强制转换访问值,请注意,这将是未对齐的内存,因此在旧计算机上速度会很慢,但是MODERN RAM上的巨大数据集会很慢。速度要快得多,因为最小化从RAM读取的大小将比未对齐的影响更大。
这不适用于当前的.NET实现,但是对于垃圾回收/托管运行时要记住的一件事是,对象的分配大小可能会在程序的整个生命周期内发生变化。例如,某些世代垃圾收集器(例如世代/后代参考计数混合收集器)仅需要在将对象从苗圃移动到成熟空间后存储某些信息。
这样就不可能创建可靠的通用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
使用有命令的罢工之子ObjSize。
请注意,实际消耗的内存总是比ObjSize报表大,synkblk这是因为a 直接位于对象数据之前。
在此处阅读有关这两者的更多信息。《MSDN杂志》,2005年5月,深入到.NET Framework内部,以了解CLR如何创建运行时对象。
AFAIK,您不能不实际计算每个成员的大小(以字节为单位)。但是,成员的大小(像集合中的元素一样)是否计入对象的大小,或者指向该成员的指针是否计入对象的大小?取决于您如何定义它。
我遇到过这种情况,在此之前,我想根据消耗的内存来限制缓存中的对象。
好吧,如果有一些技巧可以做到这一点,我将很高兴知道这一点!
对于值类型,可以使用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;
}
}
不,这不会给您确切的内存大小。如前所述,这是不可能的。但这会给您一个粗略的估计。
请注意,这也相当慢。
我为.NET中的不同集合创建了基准测试: https //github.com/scholtz/TestDotNetCollectionsMemoryAllocation
对于具有1,000,000个对象并分配了3个属性的.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需要为每个字段使用不同的地址,如果字段确实为空/ 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标志的。
数组和字符串只是将其长度*元素大小相加。对于累积对象的累积大小,您需要实施更复杂的解决方案,其中涉及访问每个字段并检查其内容。