c# - 字典枚举关键性能

标签 c# performance dictionary enums mono

我担心使用枚举作为键的通用字典。

如下页所述,对键使用枚举将分配内存: 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/

相关文章:

c# - 如果 Controller 与 View 强绑定(bind),为什么 Controller 需要模型参数?

c# - Membership.ValidateUser 很慢

java - 基于 Java 的应用程序中的内存泄漏

Python:使用字典理解/生成器计算列表中的出现次数

c# - 字典包含键并在一个函数中获取值

c# - MemoryCache.Default 在 .NET Core 中不可用?

c# - 以下 PLINQ 代码没有改进

javascript - 从类型化数组更新 svg 路径

arrays - Swift - 从字典中删除一个数组

c# - 关于在 C# 中运行时关闭窗体