2010-03-23 5 views
11

Uso di C# 3.5 Sto cercando di generare tipi dinamici in fase di esecuzione utilizzando reflection emit. Ho utilizzato l'esempio Dynamic Query Library da Microsoft per creare un generatore di classi. Tutto funziona, il mio problema è che 100 tipi generati gonfiano l'utilizzo della memoria di circa 25 MB. Questo è un profilo di memoria del tutto inaccettabile, poiché alla fine voglio supportare il fatto di avere centinaia di migliaia di tipi generati in memoria.Reflect.Emit Dynamic Type Memory Blowup

Il profilo di memoria mostra che la memoria è apparentemente trattenuta da vari System.Reflection.Emit tipi e metodi sebbene non riesca a capire perché. Non ho trovato altri che parlassero di questo problema, quindi spero che qualcuno in questa comunità sappia cosa sto facendo male o se questo è un comportamento previsto.

Escogitato Esempio di seguito:

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 questo è in realtà un po 'peggio in .NET 4.0. – Firestrand

+0

È possibile avere prestazioni migliori utilizzando CCI o Mono.Cecil. – leppie

+0

@leppie grazie per il suggerimento CCI. Sto cercando di usare quello invece. Sono scomodo dover usare la riflessione come sono per risolvere il problema. – Firestrand

risposta

4

Purtroppo , c'è un campo statico in ModuleBuilder che tiene sulla memoria e che non otterrà mai GC'd. Non riesco a ricordare quale campo e cosa contenesse ora, ma questo può essere visto da SOS in WinDbg.

La buona notizia è che .NET 4 supporta assemblaggi dinamici GC-grado :)

+1

@leppie Si scopre che ogni tipo dinamico nell'assieme mantiene un riferimento a ModuleBuilder e al successivo TypeBuilder che utilizza per la risoluzione dei conflitti di tipo Nome. Se si assicura che non vi siano conflitti di nomi, è possibile cancellare l'elenco di TypeBuilder e liberare tutta la memoria senza effetti collaterali negativi. (Almeno finora) – Firestrand

+0

@Firestrand: Grazie per le informazioni :) – leppie

+0

@leppie Come posso chiamare "Cancella" sulla variabile privata ModuleBuilder m__TypeBuilderList? –

1

Bene, la prima cosa che nota è che si sta creando un nuovo stabilimento, e quindi nuova AssemblyBuilder, ogni iterazione. È possibile riutilizzare la fabbrica (creando più tipi nello stesso assemblaggio dinamico)?

+1

Grande presa, in realtà non fa differenza. Aggiornerò il codice qui in un secondo. – Firestrand

1

Indipendentemente dal problema reale che si sta vedendo ora, raccomando vivamente contro il vostro attuale approccio. Reflection.Emit non è stato progettato per supportare la creazione di centinaia di migliaia di tipi (ad esempio, vedere this connect issue, sebbene quel particolare problema si possa applicare solo se li si inserisce tutti in un unico assieme dinamico). Perché dovresti creare così tanti tipi?

+0

Grazie per il link. Metterò questo problema alla connessione e non sono riuscito a trovare una soluzione. La ragione di questo molti tipi dinamici è che ho bisogno di supportare le capacità di molti client per connettersi a un'applicazione e creare nuovi tipi in fase di esecuzione, quindi devo essere in grado di persistere e recuperare quei tipi da un database. Sfortunatamente con questo problema di memoria dovrò cercare altre soluzioni su come farlo. – Firestrand

+0

@Firestrand - Riesco a comprendere la necessità di creare alcuni tipi dinamicamente in fase di runtime, ma un centinaio di migliaia di tipi è __a molto__. Ad esempio, mscorlib ne ha solo un paio di migliaia. – kvb

+0

Vero diverse centinaia di migliaia è un limite superiore, ma durante i test con la perdita di memoria evidente anche un migliaio sarebbe proibitivo. – Firestrand

4

Questa sembra essere una perdita di memoria effettiva in System.Reflection.Emit. NUOVA SOLUZIONE SOTTO Sono stato in grado di eliminare la maggior parte della memoria esaurita utilizzando la riflessione e un processo di smaltimento manuale. Ho usato i metodi di estensione per aggiungere un metodo Dispose su alcuni tipi. Questo non ripulisce tutto ma il codice mostra come farlo. Sto passando a un modo diverso per ottenere il risultato di cui ho bisogno. Il codice è qui per chi è interessato a come farlo.

Nell'esempio originale si chiamerebbe tb.Dispose() sull'istanza TypeBuilder dopo aver generato il tipo. I metodi di estensione sono riportati di seguito, ricorda QUESTO NON PULISCE TUTTO ma ottiene la maggior parte della memoria liberata. Anche questo codice non è ottimizzato per la velocità. Ci sono modi per accelerare il riflesso usato, questo è solo un esempio. Utilizzare a proprio rischio.

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 Trovato la causa effettiva: Sembra che ogni tipo creato nell'assieme dinamica contiene un riferimento alla ModuleBuilder (in Type.Module), che a sua volta mantiene un elenco di TypeBuilder oggetti. Questo elenco viene scansionato ogni volta che viene aggiunto un tipo per verificare conflitti di nome. Se si mantiene un HashSet fuori dalla routine generazione tipo per essere sicuri di non avere alcun conflitto di nomi è possibile chiamare Clear sul ModuleBuilder variabile privata m__TypeBuilderList dopo la Type è generato senza effetti collaterali negativi (finora)

+0

Come posso chiamare "Cancella" sulla variabile privata ModuleBuilder m__TypeBuilderList? –

+0

In .NET 4.0 questo è cambiato in un 'Dictionary ' (dove 'Type' è in realtà' TypeBuilder') con il nome 'm_TypeBuilderDict'. –