First, I am aware of many posted questions covering the topic: 1 2 3 4 5. The proposed approaches & why not:
- Marshal.SizeOf() 1 - does not work for managed types.
- GC.GetTotalMemory 1 2 - race condition prone.
- Serialization 1 2 3 4 - it's quite close, but automatic-fields can be problematic, as well as properties without public setter. Also, it's not optimal performance-wise.
- Code profiling using SOS 1 2 and other tools - great, but not for runtime.
Due to padding and issues posted 1 2 3, it appears, there is no optimal solution, rather trade-off between precision, performance and code-bloat.
However, I needed simple method to count optimistic (minimum) memory usage, i.e. I want to know that the object occupies at least that much. The rationale is that I have the environment of types owning many collections, sometimes nested, and I want to approximate quickly, that an object is coming close to i.e. .5 GB in memory etc..
There is what I came up with & my questions:
- I am looking forward for Your thoughts and suggestions on what can be done better.
- Especially, I'm looking for memory that is not accounted for in this code, and could be (without writing 200+ lines of code for this).
I can't get auto generated fields (property
__BackingField
) for inherited type, whilst it works fine for not inherited backing fields. I searched for proper BindingFlag, but could not find one.public static long SizeInBytes<T>(this T someObject) { var temp = new Size<T>(someObject); var tempSize = temp.GetSizeInBytes(); return tempSize; } /// <summary> /// A way to estimate the in-memory size af any menaged object /// </summary> /// <typeparam name="TT"></typeparam> private sealed class Size<TT> { private static readonly int PointerSize = Environment.Is64BitOperatingSystem ? sizeof(long) : sizeof(int); private readonly TT _obj; private readonly HashSet<object> _references; public Size(TT obj) { _obj = obj; _references = new HashSet<object> { _obj }; } public long GetSizeInBytes() { return GetSizeInBytes(_obj); } private long GetSizeInBytes<T>(T obj) { if (obj == null) return sizeof(int); var type = obj.GetType(); if (type.IsPrimitive) { switch (Type.GetTypeCode(type)) { case TypeCode.Boolean: case TypeCode.Byte: case TypeCode.SByte: return sizeof(byte); case TypeCode.Char: return sizeof(char); case TypeCode.Single: return sizeof(float); case TypeCode.Double: return sizeof(double); case TypeCode.Int16: case TypeCode.UInt16: return sizeof(short); case TypeCode.Int32: case TypeCode.UInt32: return sizeof(int); case TypeCode.Int64: case TypeCode.UInt64: default: return sizeof(long); } } if (obj is decimal) { return sizeof(decimal); } if (obj is string) { return sizeof(char) * obj.ToString().Length; } if (type.IsEnum) { return sizeof(int); } if (type.IsArray) { long sizeTemp = PointerSize; var casted = (IEnumerable)obj; foreach (var item in casted) { sizeTemp += GetSizeInBytes(item); } return sizeTemp; } if (obj is Pointer) { return PointerSize; } long size = 0; var t = type; while (t != null) { size += PointerSize; var fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); foreach (var field in fields) { var tempVal = field.GetValue(obj); if (!_references.Contains(tempVal)) { _references.Add(tempVal); size += GetSizeInBytes(tempVal); } } t = t.BaseType; } return size; } }
[EDIT]
This question resulted in Nuget and cp article