2010-03-23 2 views
11

Использование C# 3.5 Я пытаюсь создать динамические типы во время выполнения с использованием отражения. Я использовал образец Dynamic Query Library от Microsoft для создания генератора классов. Все работает, моя проблема в том, что 100 сгенерированных типов раздувают использование памяти примерно на 25 МБ. Это совершенно неприемлемый профиль памяти, поскольку в конечном итоге я хочу поддерживать несколько сотен тысяч типов, сгенерированных в памяти.Reflect.Emit Dynamic Type Memory Blowup

Профилирование памяти показывает, что память, по-видимому, хранится различными типами и методами System.Reflection.Emit, хотя я не могу понять, почему. Я не нашел других, говорящих об этой проблеме, поэтому я надеюсь, что кто-то из этого сообщества либо знает, что я делаю неправильно, либо если это ожидаемое поведение.

надуманный пример ниже:

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 это на самом деле немного хуже в .NET 4.0. – Firestrand

+0

У вас может быть более высокая производительность с использованием CCI или Mono.Cecil. – leppie

+0

@leppie спасибо за предложение CCI. Вместо этого я использую это вместо этого. Мне неудобно использовать рефлексию, как я должен решить проблему. – Firestrand

ответ

4

К сожалению , есть статическое поле в ModuleBuilder, удерживающее память, и это никогда не получит GC'd. Я не могу вспомнить, какое поле и что он сейчас содержит, но это видно из SOS в WinDbg.

Хорошая новость заключается в том, что .NET 4 поддерживает GC-способные динамические сборки :)

+1

@leppie Оказывается, каждый динамический тип в сборке сохраняет ссылку на ModuleBuilder и последующий TypeBuilder, который он использует для разрешения конфликта имен типа. Если вы уверены, что конфликтов имен нет, вы можете очистить список TypeBuilder и освободить всю память без каких-либо отрицательных побочных эффектов. (По крайней мере, до сих пор) – Firestrand

+0

@Firestrand: Спасибо за информацию :) – leppie

+0

@leppie Как я могу вызвать «Очистить» в private varible ModuleBuilder m__TypeBuilderList? –

1

Ну, первое, что я отмечаю, что вы создаете новый завод, и, следовательно, новый AssemblyBuilder, каждую итерацию. Можете ли вы повторно использовать фабрику (создание нескольких типов в одной динамической сборке)?

+1

Отличный улов, на самом деле это не имеет никакого значения. Я обновляю код здесь через секунду. – Firestrand

1

Независимо от фактической проблемы, которую вы сейчас видите, я настоятельно рекомендую против вашего текущего подхода. Reflection.Emit не был предназначен для поддержки создания сотен тысяч типов (например, см. this connect issue, хотя эта конкретная проблема может применяться только в том случае, если вы помещаете их в одну динамическую сборку). Зачем вам нужно создавать много типов?

+0

Спасибо за ссылку. Я поставил эту проблему на подключение, а также не смог найти решение. Причина для многих динамических типов заключается в том, что мне необходимо поддерживать способности многих клиентов подключаться к приложению и создавать новые типы во время выполнения, тогда мне нужно уметь сохранять и извлекать эти типы из базы данных. К сожалению, с этой проблемой памяти мне придется посмотреть другие решения, как это сделать. – Firestrand

+0

@Firestrand - Я могу понять необходимость динамического создания некоторых типов во время выполнения, но сто тысяч типов - __a lot__. Например, у mscorlib всего несколько тысяч. – kvb

+0

Правда несколько сотен тысяч - это верхний предел, но во время тестирования с утечкой памяти очевидно, что даже тысяча будет запретительной. – Firestrand

4

Это, по-видимому, фактическая утечка памяти в System.Reflection.Emit. NEW SOLUTION НИЖЕ Я смог избавиться от большей части памяти, используемой при использовании рефлексии и ручного процесса утилизации. Я использовал методы расширения для добавления метода Dispose для некоторых типов. Это не очищает все, но код показывает, как это сделать. Я перехожу к другому способу получения нужного результата. Код здесь для тех, кто заинтересован в том, как это сделать.

В исходном образце вы вызываете tb.Dispose() в свой экземпляр TypeBuilder после того, как вы сгенерировали этот тип. Методы расширения ниже, помните ЭТО НЕ ЧИТАЕТ ВСЕ ВСЕ, но освобождает большую часть памяти. Этот код также не оптимизирован для скорости. Есть способы ускорить используемое отражение, это всего лишь пример. Используйте на свой страх и риск.

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 Найдена фактическая причина: Оказывается, что каждый тип создан в динамической сборке содержит ссылку на ModuleBuilderType.Module), который в своей очереди хранит список TypeBuilder объектов. Этот список проверяется каждый раз, когда Type добавляется для проверки конфликтов имен. Если вы держите HashSet из рутины поколения типа, чтобы убедиться, что вы не получаете никаких конфликтов имен вы можете позвонить Очистить на ModuleBuilder частной переменной m__TypeBuilderList после Type генерируются без каких-либо негативных побочных эффектов (So Far)

+0

Как я могу вызвать «Очистить» в private varible ModuleBuilder m__TypeBuilderList? –

+0

В .NET 4.0 это изменилось на 'Dictionary ' (где 'Type' на самом деле' TypeBuilder') с именем 'm_TypeBuilderDict'. –