Reflect.Emit 动态类型内存爆炸
使用 C# 3.5,我尝试使用反射发射在运行时生成动态类型。我使用 Microsoft 的动态查询库示例来创建类生成器。一切正常,我的问题是 100 个生成的类型使内存使用量增加了大约 25MB。这是一个完全不可接受的内存配置文件,因为最终我想支持在内存中生成数十万种类型。
内存分析显示内存显然由各种 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; }
}
}
}
Using C# 3.5 I am trying to generate dynamic types at runtime using reflection emit. I used the Dynamic Query Library sample from Microsoft to create a class generator. Everything works, my problem is that 100 generated types inflate the memory usage by approximately 25MB. This is a completely unacceptable memory profile as eventually I want to support having several hundred thousand types generated in memory.
Memory profiling shows that the memory is apparently being held by various System.Reflection.Emit types and methods though I can't figure out why. I haven't found others talking about this problem so I am hoping someone in this community either knows what I am doing wrong or if this is expected behavior.
Contrived Example below:
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; }
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
不幸的是,
ModuleBuilder
中有一个静态字段占用内存,并且永远不会被 GC 回收。我现在不记得哪个字段以及它包含什么内容,但这可以从 WinDbg 的 SOS 中看到。好消息是 .NET 4 支持可 GC 的动态程序集:)
Unfortunately, there is a static field in
ModuleBuilder
holding onto the memory, and that will never get GC'd. I cant recall which field and what it contained now, but this can be seen from within SOS in WinDbg.The good news is that .NET 4 supports GC-able dynamic assemblies :)
这似乎是 System.Reflection.Emit 中的实际内存泄漏。 下面的新解决方案 我能够通过使用反射和手动处置过程来摆脱大部分用完的内存。我使用扩展方法在某些类型上添加 Dispose 方法。这并不能清理所有内容,但代码显示了如何做到这一点。我正在转向一种不同的方式来获得我需要的结果。对于那些对如何操作感兴趣的人来说,这里提供了代码。
在原始示例中,生成类型后,您将在 TypeBuilder 实例上调用 tb.Dispose() 。扩展方法如下,请记住这不会清理所有内容,但确实会释放大部分内存。该代码也没有针对速度进行优化。有一些方法可以用来加速反射,这只是一个例子。 使用风险自负。
编辑
找到了实际原因:看来动态程序集中创建的每个类型都包含对
ModuleBuilder
(在Type.Module
中)的引用,而后者又保留了的列表>TypeBuilder
对象。每次添加类型时都会扫描此列表以检查名称冲突。如果将HashSet
保留在类型生成例程之外以确保不会发生任何名称冲突,则可以对ModuleBuilder
私有变量m__TypeBuilderList
调用 Clear生成Type
后没有任何负面影响(到目前为止)This appears to be an actual memory leak in System.Reflection.Emit. NEW SOLUTION BELOW I was able to get rid of most of the memory used up by using reflection and a manual dispose process. I used extension methods to add a Dispose method on some of the types. This doesn't clean up everything but the code shows how to do it. I am moving on to a different way of getting the result I need. The code is here for those interested in how to do it.
In the original sample you would call
tb.Dispose()
on your TypeBuilder instance after you have generated the type. The extension methods are below, remember THIS DOES NOT CLEAN UP EVERYTHING but does get most of the memory freed. This code is also not optimized for speed. There are ways to speed up the reflection used, this is just an example. Use at your own risk.EDIT
Found the actual cause: It appears that each Type created in the dynamic assembly holds a reference to the
ModuleBuilder
(inType.Module
) which in turn keeps a list ofTypeBuilder
objects. This list is scanned each time a Type is added to check for name conflicts. If you keep aHashSet
out of the Type generation routine to ensure you don't get any name conflicts you can call Clear on theModuleBuilder
private variablem__TypeBuilderList
after theType
is generated without any negative side effects (So Far)好吧,我注意到的第一件事是,您正在创建一个新工厂,因此每次迭代都会创建新的
AssemblyBuilder
。您可以重复使用工厂(在同一个动态程序集中创建多种类型)吗?Well, the first thing I note is that you are creating a new factory, and hence new
AssemblyBuilder
, each iteration. Can you re-use the factory (creating multiple types in the same dynamic assembly)?无论您现在看到的实际问题是什么,我都强烈建议您不要采用当前的方法。 Reflection.Emit 的设计初衷并非支持创建数十万种类型(例如,请参阅 此连接问题,尽管该特定问题可能仅在您将它们全部放入单个动态程序集中时才适用)。为什么需要创建那么多类型?
Regardless of the actual issue you are seeing now, I'd strongly recommend against your current approach. Reflection.Emit was not designed to support the creation of hundreds of thousands of types (e.g. see this connect issue, although that particular issue may only apply if you're putting them all into a single dynamic assembly). Why would you need to create that many types?