4

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:

  1. I am looking forward for Your thoughts and suggestions on what can be done better.
  2. 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).
  3. 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

Piotr Falkowski
  • 1,957
  • 2
  • 16
  • 24
  • Possibly **too broad** due to _"There are either too many possible answers, or good answers would be too long for this format. Please add details to narrow the answer set or to isolate an issue that can be answered in a few paragraphs"_ –  Nov 10 '14 at 02:09
  • 1
    Rather than all of the conditional statements, it may be simpler to use overloading to handle the primitive types. – JohnLBevan Nov 10 '14 at 02:13
  • 1
    You should use `GetTypeCode` with switch/case instead to avoid waterfall if-else. http://msdn.microsoft.com/en-us/library/system.typecode(v=vs.110).aspx – tia Nov 10 '14 at 02:17
  • 2
    It is always fun to try to compute size of objects with circular references... :) – Alexei Levenkov Nov 10 '14 at 02:41
  • Thanks @tia, I totally forgot about GetTypeCode. – Piotr Falkowski Nov 10 '14 at 02:43
  • I wrote a Reflection based "GC" algorithm that transitively follows all object references and returns me all reachable objects and their internal size. This is useful when debugging memory leaks. This would give you a precise number for the memory use of your object graph. – usr Nov 10 '14 at 10:12
  • Thanks @Alexei, your point is very good. I fixed this for most common cases. – Piotr Falkowski Nov 10 '14 at 10:45
  • Thanks @usr. I'm impressed by your profile to the point of being overwhelmed, and I could not find the algorithm you mentioned. Could you point me to it, please? – Piotr Falkowski Nov 10 '14 at 11:46
  • The code is not online and it is too long to be posted here. Just an idea. – usr Nov 10 '14 at 12:07
  • 1
    When you are computing the size of string, you have to take into account also the size of the length member. So, it should be sizeof(char)*string.Length + sizeof(int). – Zoltan Tirinda Nov 29 '15 at 07:11

1 Answers1

3

To answer your third question about getting fields, you can reliably get all fields in a type like this:

    public static IEnumerable<FieldInfo> GetAllFields(Type t)
    {
        while (t != null)
        {
            foreach (FieldInfo field in t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly))
            {
                yield return field;
            }
            t = t.BaseType;
        }
    }

This works because GetFields can return the private fields of the current Type, but not any inherited private fields; so you need to walk up the inheritance chain calling GetFields on each Type.

user12864
  • 581
  • 4
  • 7