Utilisation C # 3.5 J'essaie de générer des types dynamiques au moment de l'exécution à l'aide d'émetteur de réflexion. J'ai utilisé le Library de requête dynamique échantillon de Microsoft pour créer un générateur de classe. Tout fonctionne, mon problème est que 100 types générés gonflent l'utilisation de la mémoire d'environ 25 Mo. Il s'agit d'un profil de mémoire totalement inacceptable comme étant éventuellement que je souhaite prendre en charge plusieurs centaines de milliers de types générés en mémoire.
Le profilage de la mémoire montre que la mémoire est apparemment tenue par divers types de systèmes.Reflection.emit de types et de méthodes, même si je ne pouvais pas comprendre pourquoi. Je n'ai pas trouvé d'autres en parlant de ce problème, alors j'espère que quelqu'un dans cette communauté sait soit ce que je fais mal que je fais mal ou si cela est un comportement attendu. P>
Exemple de contreveille ci-dessous: P>
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; } } } }
4 Réponses :
Eh bien, la première chose à noter est que vous créez une nouvelle usine, et donc de nouveau AssemblyBuilder code>, chaque itération. Pouvez-vous réutiliser l'usine (créer plusieurs types dans la même assemblée dynamique)? P>
Grande prise, cela ne fait aucune différence. Je vais mettre à jour le code ici dans une seconde.
Quel que soit le problème réel que vous voyez maintenant, je recommanderais vivement à votre approche actuelle. Reflet.emit n'a pas été conçu pour soutenir la création de centaines de milliers de types (par exemple, voir Ce problème Connect , bien que ce problème particulier ne s'applique que si vous les mettez tous dans un seul assemblage dynamique). Pourquoi auriez-vous besoin de créer ce nombre de types? P>
Merci pour le lien. Je vais mettre ce problème sur Connect ainsi que je n'ai pas été incapable de trouver une solution. La raison de ces types dynamiques est que je dois prendre en charge les capacités de nombreux clients pour se connecter à une application et créer de nouveaux types au moment de l'exécution, j'ai alors besoin de pouvoir persister et récupérer ces types d'une base de données. Malheureusement, avec ce problème de mémoire, je devrai regarder d'autres solutions pour comment faire cela.
@Firetrand - Je peux comprendre la nécessité de créer des types de manière dynamique au moment de l'exécution, mais cent mille types est beaucoup b>. Par exemple, MSCorlib a juste quelques milliers de Total.
Vrai plusieurs centaines de milliers est une limite supérieure, mais pendant les tests avec la fuite de mémoire évidente, même mille prohibitif.
Ceci semble être une fuite de mémoire réelle dans System.Reflection.emit. Nouvelle solution ci-dessous Strong> J'ai pu se débarrasser de la majeure partie de la mémoire utilisée à l'aide de la réflexion et d'un processus manuel d'élimination. J'ai utilisé des méthodes d'extension pour ajouter une méthode d'élimination sur certains types. Cela ne nettoie pas tout, mais le code montre comment le faire. Je passe à une façon différente d'obtenir le résultat dont j'ai besoin. Le code est ici pour ceux qui intéressent comment le faire. Dans l'exemple d'origine, vous appelleriez edit strong>
Trouvé la cause réelle: il apparaît que chaque type créé dans l'assembly dynamique contient une référence au ModuleBuilder code> (dans tb.dispose () code> sur votre instance de typeBuilder après avoir généré le type. Les méthodes d'extension sont ci-dessous, rappelez-vous
type.module code>) qui maintient à son tour une liste de
TypeBuilder Code> Objets. Cette liste est numérisée chaque fois qu'un type est ajouté pour vérifier les conflits de noms. Si vous conservez un
hashset code> hors de la routine de génération de type pour vous assurer de ne pas obtenir de conflits de noms, vous pouvez appeler clairement sur la variable
ModuleBuilder Code> Variable privée
M__TypeBuilderList Code> Après que le type
code> est généré sans aucun effet secondaire négatif (jusqu'à présent) p> p>
Comment puis-je appeler "Effacer" sur la variable privée de Module Builder M_ TypeBuilderList?
Dans .NET 4.0 Ceci changé en un dictionnaire Dictionnaire
Type code> est en réalité code> typebuilder code>) avec le nom
m_typebuilderdict < / code>.
Malheureusement, il existe un champ statique dans La bonne nouvelle est que .NET 4 prend en charge les assemblages dynamiques de GC-apte :) p> ModuleBuilder Code> Tenir la mémoire, et cela ne recevra jamais GC'D. Je ne peux pas me rappeler quel champ et ce qu'il contient maintenant, mais cela peut être vu de l'intérieur de SOS dans Windbg. P>
@Leppie Il s'avère que chaque type dynamique de l'assembly conserve une référence au moduleBuilder et à la suite de typeBuilder qu'il utilise pour la résolution de conflit de nom de type. Si vous vous assurez qu'il n'y a pas de conflit de noms, vous pouvez effacer la liste de typesBuilder et libérer toute la mémoire sans aucun effet secondaire négatif. (Au moins jusqu'à présent)
@Firetrand: merci pour l'info :)
@Leppie Comment puis-je appeler "Effacer" sur la variable privée de Module Builder M_ TypeBuilderList?
FYI C'est en fait un peu pire dans .NET 4.0.
Vous pouvez avoir de meilleures performances en utilisant la CCI ou le mono.cecil.
@Leppie merci pour la suggestion CCI. Je cherche à l'aide de ça à la place. Je suis mal à l'aise d'avoir à utiliser la réflexion comme je dois résoudre le problème.
@Leppie FYI CCI ne supporte pas actuellement la possibilité d'émettre une assemblée dynamique. Je suppose que je pourrais persister, charger et muter à la place, mais cela a des connotations négatives pour l'environnement que je travaille.
Je pense que les assemblées dynamiques internes font la même chose. Mais c'est une préoccupation de la mienne aussi.