Tutorial :Reflect.Emit Dynamic Type Memory Blowup



Question:

Using C# 3.5 I am trying to generate dynamic types at runtime using reflection emit. I used the Dynamic Query Library sample from Microsoft to create a class generator. Everything works, my problem is that 100 generated types inflate the memory usage by approximately 25MB. This is a completely unacceptable memory profile as eventually I want to support having several hundred thousand types generated in memory.

Memory profiling shows that the memory is apparently being held by various System.Reflection.Emit types and methods though I can't figure out why. I haven't found others talking about this problem so I am hoping someone in this community either knows what I am doing wrong or if this is expected behavior.

Contrived Example below:

using System;  using System.Collections.Generic;  using System.Text;  using System.Reflection;  using System.Reflection.Emit;    namespace SmallRelfectExample  {      class Program      {          static void Main(string[] args)          {              int typeCount = 100;              int propCount = 100;              Random rand = new Random();              Type dynType = null;              SlimClassFactory scf = new SlimClassFactory();              for (int i = 0; i < typeCount; i++)              {                  List<DynamicProperty> dpl = new List<DynamicProperty>(propCount);                  for (int j = 0; j < propCount; j++)                  {                      dpl.Add(new DynamicProperty("Key" + rand.Next().ToString(), typeof(String)));                  }                  dynType = scf.CreateDynamicClass(dpl.ToArray(), i);                  //Optionally do something with the type here              }              Console.WriteLine("SmallRelfectExample: {0} Types generated.", typeCount);              Console.ReadLine();          }      }      public class SlimClassFactory      {          private readonly ModuleBuilder module;          public SlimClassFactory()          {              AssemblyName name = new AssemblyName("DynamicClasses");              AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);              module = assembly.DefineDynamicModule("Module");            }          public Type CreateDynamicClass(DynamicProperty[] properties, int Id)          {              string typeName = "DynamicClass" + Id.ToString();              TypeBuilder tb = module.DefineType(typeName, TypeAttributes.Class |                  TypeAttributes.Public, typeof(DynamicClass));              FieldInfo[] fields = GenerateProperties(tb, properties);              GenerateEquals(tb, fields);              GenerateGetHashCode(tb, fields);              Type result = tb.CreateType();              return result;          }          static FieldInfo[] GenerateProperties(TypeBuilder tb, DynamicProperty[] properties)          {              FieldInfo[] fields = new FieldBuilder[properties.Length];              for (int i = 0; i < properties.Length; i++)              {                  DynamicProperty dp = properties[i];                  FieldBuilder fb = tb.DefineField("_" + dp.Name, dp.Type, FieldAttributes.Private);                  PropertyBuilder pb = tb.DefineProperty(dp.Name, PropertyAttributes.HasDefault, dp.Type, null);                  MethodBuilder mbGet = tb.DefineMethod("get_" + dp.Name,                      MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,                      dp.Type, Type.EmptyTypes);                  ILGenerator genGet = mbGet.GetILGenerator();                  genGet.Emit(OpCodes.Ldarg_0);                  genGet.Emit(OpCodes.Ldfld, fb);                  genGet.Emit(OpCodes.Ret);                  MethodBuilder mbSet = tb.DefineMethod("set_" + dp.Name,                      MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,                      null, new Type[] { dp.Type });                  ILGenerator genSet = mbSet.GetILGenerator();                  genSet.Emit(OpCodes.Ldarg_0);                  genSet.Emit(OpCodes.Ldarg_1);                  genSet.Emit(OpCodes.Stfld, fb);                  genSet.Emit(OpCodes.Ret);                  pb.SetGetMethod(mbGet);                  pb.SetSetMethod(mbSet);                  fields[i] = fb;              }              return fields;          }          static void GenerateEquals(TypeBuilder tb, FieldInfo[] fields)          {              MethodBuilder mb = tb.DefineMethod("Equals",                  MethodAttributes.Public | MethodAttributes.ReuseSlot |                  MethodAttributes.Virtual | MethodAttributes.HideBySig,                  typeof(bool), new Type[] { typeof(object) });              ILGenerator gen = mb.GetILGenerator();              LocalBuilder other = gen.DeclareLocal(tb);              Label next = gen.DefineLabel();              gen.Emit(OpCodes.Ldarg_1);              gen.Emit(OpCodes.Isinst, tb);              gen.Emit(OpCodes.Stloc, other);              gen.Emit(OpCodes.Ldloc, other);              gen.Emit(OpCodes.Brtrue_S, next);              gen.Emit(OpCodes.Ldc_I4_0);              gen.Emit(OpCodes.Ret);              gen.MarkLabel(next);              foreach (FieldInfo field in fields)              {                  Type ft = field.FieldType;                  Type ct = typeof(EqualityComparer<>).MakeGenericType(ft);                  next = gen.DefineLabel();                  gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null);                  gen.Emit(OpCodes.Ldarg_0);                  gen.Emit(OpCodes.Ldfld, field);                  gen.Emit(OpCodes.Ldloc, other);                  gen.Emit(OpCodes.Ldfld, field);                  gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("Equals", new Type[] { ft, ft }), null);                  gen.Emit(OpCodes.Brtrue_S, next);                  gen.Emit(OpCodes.Ldc_I4_0);                  gen.Emit(OpCodes.Ret);                  gen.MarkLabel(next);              }              gen.Emit(OpCodes.Ldc_I4_1);              gen.Emit(OpCodes.Ret);          }          static void GenerateGetHashCode(TypeBuilder tb, FieldInfo[] fields)          {              MethodBuilder mb = tb.DefineMethod("GetHashCode",                  MethodAttributes.Public | MethodAttributes.ReuseSlot |                  MethodAttributes.Virtual | MethodAttributes.HideBySig,                  typeof(int), Type.EmptyTypes);              ILGenerator gen = mb.GetILGenerator();              gen.Emit(OpCodes.Ldc_I4_0);              foreach (FieldInfo field in fields)              {                  Type ft = field.FieldType;                  Type ct = typeof(EqualityComparer<>).MakeGenericType(ft);                  gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null);                  gen.Emit(OpCodes.Ldarg_0);                  gen.Emit(OpCodes.Ldfld, field);                  gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("GetHashCode", new Type[] { ft }), null);                  gen.Emit(OpCodes.Xor);              }              gen.Emit(OpCodes.Ret);          }      }      public abstract class DynamicClass      {          public override string ToString()          {              PropertyInfo[] props = GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);              StringBuilder sb = new StringBuilder();              sb.Append("{");              for (int i = 0; i < props.Length; i++)              {                  if (i > 0) sb.Append(", ");                  sb.Append(props[i].Name);                  sb.Append("=");                  sb.Append(props[i].GetValue(this, null));              }              sb.Append("}");              return sb.ToString();          }      }      public class DynamicProperty      {          private readonly string name;          private readonly Type type;            public DynamicProperty(string name, Type type)          {              if (name == null) throw new ArgumentNullException("name");              if (type == null) throw new ArgumentNullException("type");              this.name = name;              this.type = type;          }            public string Name          {              get { return name; }          }            public Type Type          {              get { return type; }          }      }  }  


Solution:1

Unfortunately, there is a static field in ModuleBuilder holding onto the memory, and that will never get GC'd. I cant recall which field and what it contained now, but this can be seen from within SOS in WinDbg.

The good news is that .NET 4 supports GC-able dynamic assemblies :)


Solution:2

This appears to be an actual memory leak in System.Reflection.Emit. NEW SOLUTION BELOW I was able to get rid of most of the memory used up by using reflection and a manual dispose process. I used extension methods to add a Dispose method on some of the types. This doesn't clean up everything but the code shows how to do it. I am moving on to a different way of getting the result I need. The code is here for those interested in how to do it.

In the original sample you would call tb.Dispose() on your TypeBuilder instance after you have generated the type. The extension methods are below, remember THIS DOES NOT CLEAN UP EVERYTHING but does get most of the memory freed. This code is also not optimized for speed. There are ways to speed up the reflection used, this is just an example. Use at your own risk.

  public static void Dispose(this TypeBuilder tb)          {              if (tb == null)                  return;              Type tbType = typeof(TypeBuilder);              FieldInfo tbMbList = tbType.GetField("m_listMethods", BindingFlags.Instance | BindingFlags.NonPublic); //List<MethodBuilder>              FieldInfo tbDecType = tbType.GetField("m_DeclaringType", BindingFlags.Instance | BindingFlags.NonPublic);//TypeBuilder              FieldInfo tbGenType = tbType.GetField("m_genTypeDef", BindingFlags.Instance | BindingFlags.NonPublic);//TypeBuilder              FieldInfo tbDeclMeth = tbType.GetField("m_declMeth", BindingFlags.Instance | BindingFlags.NonPublic);//MethodBuilder              FieldInfo tbMbCurMeth = tbType.GetField("m_currentMethod", BindingFlags.Instance | BindingFlags.NonPublic);//MethodBuilder              FieldInfo tbMod = tbType.GetField("m_module", BindingFlags.Instance | BindingFlags.NonPublic);//ModuleBuilder              FieldInfo tbGenTypeParArr = tbType.GetField("m_inst", BindingFlags.Instance | BindingFlags.NonPublic); //GenericTypeParameterBuilder[]                 TypeBuilder tempDecType = tbDecType.GetValue(tb) as TypeBuilder;              tempDecType.Dispose();              tbDecType.SetValue(tb, null);              tempDecType = tbGenType.GetValue(tb) as TypeBuilder;              tempDecType.Dispose();              tbDecType.SetValue(tb, null);                MethodBuilder tempMeth = tbDeclMeth.GetValue(tb) as MethodBuilder;              tempMeth.Dispose();              tbDeclMeth.SetValue(tb,null);              tempMeth = tbMbCurMeth.GetValue(tb) as MethodBuilder;              tempMeth.Dispose();              tbMbCurMeth.SetValue(tb, null);                ArrayList mbList = tbMbList.GetValue(tb) as ArrayList;              for (int i = 0; i < mbList.Count; i++)              {                  tempMeth = mbList[i] as MethodBuilder;                  tempMeth.Dispose();                  mbList[i] = null;              }              tbMbList.SetValue(tb, null);                ModuleBuilder tempMod = tbMod.GetValue(tb) as ModuleBuilder;              tempMod.Dispose();              tbMod.SetValue(tb, null);                tbGenTypeParArr.SetValue(tb, null);          }          public static void Dispose(this MethodBuilder mb)          {              if (mb == null)                  return;              Type mbType = typeof(MethodBuilder);              FieldInfo mbILGen = mbType.GetField("m_ilGenerator", BindingFlags.Instance | BindingFlags.NonPublic);              //FieldInfo mbIAttr = mbType.GetField("m_iAttributes", BindingFlags.Instance | BindingFlags.NonPublic);              FieldInfo mbMod = mbType.GetField("m_module", BindingFlags.Instance | BindingFlags.NonPublic); //ModuleBuilder               FieldInfo mbContType = mbType.GetField("m_containingType", BindingFlags.Instance | BindingFlags.NonPublic);              FieldInfo mbLocSigHelp = mbType.GetField("m_localSignature", BindingFlags.Instance | BindingFlags.NonPublic);//SignatureHelper              FieldInfo mbSigHelp = mbType.GetField("m_signature", BindingFlags.Instance | BindingFlags.NonPublic);//SignatureHelper                ILGenerator tempIlGen = mbILGen.GetValue(mb) as ILGenerator;              tempIlGen.Dispose();              SignatureHelper tempmbSigHelp = mbLocSigHelp.GetValue(mb) as SignatureHelper;              tempmbSigHelp.Dispose();              tempmbSigHelp = mbSigHelp.GetValue(mb) as SignatureHelper;              tempmbSigHelp.Dispose();                ModuleBuilder tempMod = mbMod.GetValue(mb) as ModuleBuilder;              tempMod.Dispose();              mbMod.SetValue(mb, null);                mbILGen.SetValue(mb, null);              mbContType.SetValue(mb, null);              mbLocSigHelp.SetValue(mb, null);              mbSigHelp.SetValue(mb, null);              mbMod.SetValue(mb, null);          }          public static void Dispose(this SignatureHelper sh)          {              if (sh == null)                  return;              Type shType = typeof(SignatureHelper);              FieldInfo shModule = shType.GetField("m_module", BindingFlags.Instance | BindingFlags.NonPublic);              //FieldInfo shSig = shType.GetField("m_signature", BindingFlags.Instance | BindingFlags.NonPublic);              shModule.SetValue(sh, null);              //shSig.SetValue(sh, null);          }          public static void Dispose(this ILGenerator ilGen)          {              if (ilGen == null)                  return;              Type ilGenType = typeof(ILGenerator);              FieldInfo ilSigHelp = ilGenType.GetField("m_localSignature", BindingFlags.Instance | BindingFlags.NonPublic);//SignatureHelper              SignatureHelper sigTemp = ilSigHelp.GetValue(ilGen) as SignatureHelper;              sigTemp.Dispose();              ilSigHelp.SetValue(ilGen, null);          }          public static void Dispose(this ModuleBuilder modBuild)          {              if (modBuild == null)                  return;              Type modBuildType = typeof(ModuleBuilder);              FieldInfo modBuildModData = modBuildType.GetField("m__moduleData", BindingFlags.Instance | BindingFlags.NonPublic |BindingFlags.FlattenHierarchy );              FieldInfo modTypeBuildList = modBuildType.GetField("m__TypeBuilderList", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);                ArrayList modTypeList = modTypeBuildList.GetValue(modBuild) as ArrayList;              if(modTypeList != null)              {                  for (int i = 0; i < modTypeList.Count; i++)                  {                      TypeBuilder tb = modTypeList[i] as TypeBuilder;                      tb.Dispose();                      modTypeList = null;                  }                  modTypeBuildList.SetValue(modBuild, null);              }              modBuildModData.SetValue(modBuild, null);          }  

EDIT Found the actual cause: It appears that each Type created in the dynamic assembly holds a reference to the ModuleBuilder (in Type.Module) which in turn keeps a list of TypeBuilder objects. This list is scanned each time a Type is added to check for name conflicts. If you keep a HashSet out of the Type generation routine to ensure you don't get any name conflicts you can call Clear on the ModuleBuilder private variable m__TypeBuilderList after the Type is generated without any negative side effects (So Far)


Solution:3

Well, the first thing I note is that you are creating a new factory, and hence new AssemblyBuilder, each iteration. Can you re-use the factory (creating multiple types in the same dynamic assembly)?


Solution:4

Regardless of the actual issue you are seeing now, I'd strongly recommend against your current approach. Reflection.Emit was not designed to support the creation of hundreds of thousands of types (e.g. see this connect issue, although that particular issue may only apply if you're putting them all into a single dynamic assembly). Why would you need to create that many types?


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »