我担心使用枚举作为键的通用字典。
如下页所述,对键使用枚举将分配内存: http://blogs.msdn.com/b/shawnhar/archive/2007/07/02/twin-paths-to-garbage-collector-nirvana.aspx
我已经测试并确认了该行为,它在我的项目中引起了问题。为了可读性,我相信对键使用枚举非常有用,对我来说最佳解决方案是编写一个实现 IDictionary<TKey, TValue>
的类。 ,这将在内部使用整数作为键。原因是我不想更改所有现有词典以使用整数作为键,并进行隐式转换。这将是最好的性能明智的做法,但它会在一开始给我做很多工作,并且会降低可读性。
所以我尝试了几种方法,包括使用 GetHashCode
(不幸的是分配内存)来构建一个内部 Dictionary<int, TValue>
.
因此,将其总结为一个问题;谁能想到一个解决方案,我可以用它来保持 Dictionary<SomeEnum, TValue>
的可读性,同时具有 Dictionary<int, TValue>
的性能?
非常感谢任何建议。
最佳答案
问题是装箱。这是将值类型转换为对象的行为,这可能是不必要的,也可能不是。
方式Dictionary
比较键,本质上是,它将使用 EqualComparer<T>.Default
, 并调用 GetHashCode()
找到正确的桶,Equals
比较桶中是否有任何值等于我们正在寻找的值。
好处是:.NET Framework 有很好的优化,在 "Enum integers"
的情况下避免装箱.参见 CreateComparer() .作为键,您不太可能在这里看到整数和枚举之间的任何差异。
这里要注意:这不是一件容易的事,事实上,如果你深入挖掘,你会得出结论,这场战斗的四分之一是通过 CLR“黑客”实现的。如此处所示:
static internal int UnsafeEnumCast<T>(T val) where T : struct
{
// should be return (int) val; but C# does not allow, runtime
// does this magically
// See getILIntrinsicImplementation for how this happens.
throw new InvalidOperationException();
}
如果泛型有 Enum 约束,那肯定会更容易,甚至可能是很长的行 UnsafeEnumCast<T>(T val) where T : Enum->Integer
,但是……他们没有。
您可能想知道,getILIntrinsicImplementation 中到底发生了什么 EnumCast
?我也想知道。目前还不确定如何检查它。我相信它在运行时被替换为特定的 IL 代码?!
单声道
现在,回答您的问题:是的,您是对的。 Enum
作为 Mono 上的键,在紧密循环中会变慢。据我所知,这是因为 Mono 在枚举上进行装箱。你可以看看EnumIntEqualityComparer ,如您所见,它调用了 Array.UnsafeMov
这基本上是一种类型 T
成整数,通过装箱:(int)(object) instance;
.这是泛型的“经典”局限性,并且没有很好的解决方案。
方案一
实现 EqualityComparer<MyEnum>
为您的具体枚举。这将避免所有转换。
public struct MyEnumCOmparer : IEqualityComparer<MyEnum>
{
public bool Equals(MyEnum x, MyEnum y)
{
return x == y;
}
public int GetHashCode(MyEnum obj)
{
// you need to do some thinking here,
return (int)obj;
}
}
然后您需要做的就是将其传递给您的 Dictionary
:
new Dictionary<MyEnum, int>(new MyEnumComparer());
它有效,它为您提供与整数相同的性能,并避免了装箱问题。问题是,这不是通用的,并且为每个 Enum
写这个会觉得很傻。
解决方案2
编写一个通用的 Enum
比较器,并使用一些避免拆箱的技巧。我在 here 的帮助下写了这篇文章,
// todo; check if your TEnum is enum && typeCode == TypeCode.Int
struct FastEnumIntEqualityComparer<TEnum> : IEqualityComparer<TEnum>
where TEnum : struct
{
static class BoxAvoidance
{
static readonly Func<TEnum, int> _wrapper;
public static int ToInt(TEnum enu)
{
return _wrapper(enu);
}
static BoxAvoidance()
{
var p = Expression.Parameter(typeof(TEnum), null);
var c = Expression.ConvertChecked(p, typeof(int));
_wrapper = Expression.Lambda<Func<TEnum, int>>(c, p).Compile();
}
}
public bool Equals(TEnum firstEnum, TEnum secondEnum)
{
return BoxAvoidance.ToInt(firstEnum) ==
BoxAvoidance.ToInt(secondEnum);
}
public int GetHashCode(TEnum firstEnum)
{
return BoxAvoidance.ToInt(firstEnum);
}
}
方案三
现在,解决方案#2 有一个小问题,如 Expression.Compile()
在 iOS 上不是那么出名(没有运行时代码生成),一些单声道版本没有 ?? Expression.Compile
?? (不确定)。
您可以编写简单的 IL 代码来处理枚举转换并编译它。
.assembly extern mscorlib
{
.ver 0:0:0:0
}
.assembly 'enum2int'
{
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.class public auto ansi beforefieldinit EnumInt32ToInt
extends [mscorlib]System.Object
{
.method public hidebysig static int32 Convert<valuetype
.ctor ([mscorlib]System.ValueType) TEnum>(!!TEnum 'value') cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_000b: ret
}
}
为了将其编译成程序集,您必须调用:
ilasm enum2int.il /dll
其中 enum2int.il 是包含 IL 的文本文件。
您现在可以引用给定的程序集(enum2int.dll
)并调用静态方法,如下所示:
struct FastEnumIntEqualityComparer<TEnum> : IEqualityComparer<TEnum>
where TEnum : struct
{
int ToInt(TEnum en)
{
return EnumInt32ToInt.Convert(en);
}
public bool Equals(TEnum firstEnum, TEnum secondEnum)
{
return ToInt(firstEnum) == ToInt(secondEnum);
}
public int GetHashCode(TEnum firstEnum)
{
return ToInt(firstEnum);
}
}
它可能看起来是 killer 级代码,但它避免了装箱,并且它应该在 Mono
上为您提供更好的性能。 .
关于c# - 字典枚举关键性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26280788/