2010-03-23 8 views
11

Uso de C# 3.5 Estoy intentando generar tipos dinámicos en tiempo de ejecución mediante la emisión de reflexión. Usé la muestra Dynamic Query Library de Microsoft para crear un generador de clases. Todo funciona, mi problema es que 100 tipos generados inflan el uso de la memoria en aproximadamente 25 MB. Este es un perfil de memoria completamente inaceptable, ya que eventualmente quiero admitir tener varios cientos de miles de tipos generados en la memoria.Reflect.Emit Tipo dinámico Blowup de memoria

El perfilado de memoria muestra que aparentemente la memoria está siendo retenida por varios tipos y métodos de System.Reflection.Emit aunque no puedo entender por qué. No he encontrado a otros que hablen sobre este problema, así que espero que alguien de esta comunidad sepa lo que estoy haciendo mal o si este es el comportamiento esperado.

Contrived Ejemplo a continuación:

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; } 
     } 
    } 
} 
+0

FYI esto es en realidad un poco peor en .NET 4.0. – Firestrand

+0

Es posible que tenga un mejor rendimiento con CCI o Mono.Cecil. – leppie

+0

@leppie gracias por la sugerencia de CCI. Estoy buscando usar eso en su lugar. Me incomoda tener que usar la reflexión como soy para solucionar el problema. – Firestrand

Respuesta

4

Desafortunadamente , hay un campo estático en ModuleBuilder aferrándose a la memoria, y que nunca recibirá GC'd. No puedo recordar qué campo y qué contiene ahora, pero esto se puede ver desde dentro de SOS en WinDbg.

La buena noticia es que es compatible con .NET 4 montajes dinámicos GC-able :)

+1

@leppie Resulta que cada tipo dinámico en el conjunto guarda una referencia al ModuleBuilder y al TypeBuilder posterior que utiliza para la resolución de conflictos de nombre de tipo. Si se asegura de que no haya conflictos de nombres, puede borrar la lista de TypeBuilder y liberar toda la memoria sin efectos secundarios negativos. (Al menos hasta ahora) – Firestrand

+0

@Firestrand: Gracias por la información :) – leppie

+0

@leppie ¿Cómo puedo llamar "Borrar" en el ModuleBuilder privado varible m__TypeBuilderList? –

1

Bueno, lo primero que observo es que se está creando una nueva fábrica, y por lo tanto nueva AssemblyBuilder, cada iteración. ¿Se puede reutilizar la fábrica (creando múltiples tipos en el mismo conjunto dinámico)?

+1

Gran captura, en realidad no hace diferencia. Actualizaré el código aquí en un segundo. – Firestrand

1

Independientemente del problema real que está viendo ahora, lo recomiendo encarecidamente en contra de su enfoque actual. Reflection.Emit no se diseñó para admitir la creación de cientos de miles de tipos (por ejemplo, consulte this connect issue, aunque ese problema en particular solo se puede aplicar si los está colocando todos en un único conjunto dinámico). ¿Por qué necesitarías crear tantos tipos?

+0

Gracias por el enlace. Pondré este problema en conexión y no he podido encontrar una solución. La razón de que haya muchos tipos dinámicos es que necesito apoyar las capacidades para que muchos clientes se conecten a una aplicación y creen nuevos tipos en tiempo de ejecución; luego, debo ser capaz de mantener y recuperar esos tipos desde una base de datos. Lamentablemente con este problema de memoria tendré que buscar otras soluciones para saber cómo hacerlo. – Firestrand

+0

@Firestrand - Puedo entender la necesidad de crear algunos tipos dinámicamente en tiempo de ejecución, pero cien mil tipos es __a mucho__. Por ejemplo, mscorlib solo tiene un par de miles en total. – kvb

+0

Es cierto que varios cientos de miles es un límite superior, pero durante la prueba con la fuga de memoria evidente, incluso mil sería prohibitivo. – Firestrand

4

Esto parece ser una pérdida de memoria real en System.Reflection.Emit. NUEVA SOLUCIÓN POR DEBAJO DE Pude deshacerme de la mayor parte de la memoria utilizada mediante el uso de reflexión y un proceso de eliminación manual. Usé métodos de extensión para agregar un método Dispose en algunos de los tipos. Esto no limpia todo, pero el código muestra cómo hacerlo. Me estoy moviendo a una forma diferente de obtener el resultado que necesito. El código está aquí para aquellos interesados ​​en cómo hacerlo.

En el ejemplo original, debe llamar al tb.Dispose() en su instancia de TypeBuilder después de haber generado el tipo. Los métodos de extensión están a continuación, recuerde ESTO NO LIMPIA TODO, pero obtiene la mayor parte de la memoria liberada. Este código tampoco está optimizado para la velocidad. Hay formas de acelerar la reflexión utilizada, esto es solo un ejemplo. Utilice bajo su propio riesgo.

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); 
     } 

EDITAR encontrado la causa real: Parece que cada tipo creado en el conjunto de dinámica contiene una referencia a la ModuleBuilder (en Type.Module) que a su vez mantiene una lista de TypeBuilder objetos. Esta lista se escanea cada vez que se agrega un Tipo para verificar los conflictos de nombres. Si se mantiene una HashSet salir de la rutina de generación de Tipo para asegurarse de que no obtiene ningún conflictos de nombres que puede llamar Borrar en el ModuleBuilder variable privada m__TypeBuilderList después de la Type se genera sin ningún tipo de efectos secundarios negativos (hasta ahora)

+0

¿Cómo puedo llamar "Borrar" en ModuleBuilder private varible m__TypeBuilderList? –

+0

En .NET 4.0 esto cambió a 'Diccionario ' (donde 'Tipo' en realidad es' TypeBuilder') con el nombre 'm_TypeBuilderDict'. –

Cuestiones relacionadas