Reflect.Emit Динамический тип памяти Blowup - PullRequest
12 голосов
/ 24 марта 2010

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

Профилирование памяти показывает, что память, очевидно, удерживается различными типами и методами 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; }
        }
    }
}

Ответы [ 4 ]

5 голосов
/ 25 марта 2010

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

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

4 голосов
/ 25 марта 2010

Это похоже на фактическую утечку памяти в System.Reflection.Emit. НОВОЕ РЕШЕНИЕ НИЖЕ Мне удалось избавиться от большей части использованной памяти с помощью отражения и ручного удаления. Я использовал методы расширения, чтобы добавить метод 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 объектов. Этот список сканируется каждый раз, когда добавляется тип для проверки на конфликт имен. Если вы держите HashSet в подпрограмме генерации типов, чтобы избежать конфликтов имен, вы можете вызвать Clear для ModuleBuilder закрытой переменной m__TypeBuilderList после генерации Type без каких-либо отрицательных побочных эффектов ( Пока)

2 голосов
/ 24 марта 2010

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

1 голос
/ 24 марта 2010

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

...