Dictionary.Count性能

Joh*_*hnB 5 c# dictionary

这个问题似乎是无稽之谈.该行为无法可靠再现.

比较以下测试程序,我观察到以下示例中第一个和第二个之间的巨大性能差异(第一个示例比第二个示例慢10倍):

第一个例子(慢):

interface IWrappedDict {
    int Number { get; }
    void AddSomething (string k, string v);
}

class WrappedDict : IWrappedDict {
    private Dictionary<string, string> dict = new Dictionary<string,string> ();


    public void AddSomething (string k, string v) {
        dict.Add (k, v);
    }

    public int Number { get { return dict.Count; } }
}

class TestClass {
    private IWrappedDict wrappedDict;

    public TestClass (IWrappedDict theWrappedDict) {
        wrappedDict = theWrappedDict;
    }

    public void DoSomething () {
        // this function does the performance test
        for (int i = 0; i < 1000000; ++i) {
            var c = wrappedDict.Number; wrappedDict.AddSomething (...);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

第二个例子(快):

// IWrappedDict as above
class WrappedDict : IWrappedDict {
    private Dictionary<string, string> dict = new Dictionary<string,string> ();
    private int c = 0;

    public void AddSomething (string k, string v) {
        dict.Add (k, v); ++ c;
    }

    public int Number { get { return c; } }
}
// rest as above
Run Code Online (Sandbox Code Playgroud)

有趣地,差异消失(第一示例获取快以及)如果我改变构件变量的类型TestClass.wrappedDictIWrappedDictWrappedDict.我对此的解释是Dictionary.Count每次访问元素时重新计算元素,并且元素数量的潜在缓存仅由编译器优化完成.

任何人都可以证实吗?有没有办法以高效的方式获得元素的数量Dictionary

Jon*_*eet 5

不,每次使用时Dictionary.Count不会重新计算元素.字典维护计数,应该与第二个版本一样快.

我怀疑在第二个例子的测试中,你已经有了,WrappedDict而不是IWrappedDict,这实际上是关于接口成员访问(总是虚拟的)和JIT在知道具体类型时编译对属性的内联调用.

如果您仍然认为Count是问题,您应该能够编辑您的问题以显示一个简短但完整的程序,该程序演示快速和慢速版本,包括您如何计时.

  • @JohnB:正如我所说,我*怀疑*当你测试你的第二个例子时,你直接使用`WrappedDict` ...换句话说,我怀疑你的诊断是不正确的.如果你不同意,另一种证明不同的方法是编辑你的问题,以显示一个简短而完整的程序,展示其中的差异. (2认同)

Mar*_*ell 4

听起来你的时机不对;我得到:

#1: 330ms
#2: 335ms
Run Code Online (Sandbox Code Playgroud)

在 IDE 外部以发布模式运行以下命令时:

public void DoSomething(int count) {
    // this function does the performance test
    for (int i = 0; i < count; ++i) {
        var c = wrappedDict.Number; wrappedDict.AddSomething(i.ToString(), "a");
    }
}
static void Execute(int count, bool show)
{
    var obj1 = new TestClass(new WrappedDict1());
    var obj2 = new TestClass(new WrappedDict2());

    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
    GC.WaitForPendingFinalizers();
    var watch = Stopwatch.StartNew();
    obj1.DoSomething(count);
    watch.Stop();
    if(show) Console.WriteLine("#1: {0}ms", watch.ElapsedMilliseconds);

    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
    GC.WaitForPendingFinalizers();
    watch = Stopwatch.StartNew();
    obj2.DoSomething(count);
    watch.Stop();
    if(show) Console.WriteLine("#2: {0}ms", watch.ElapsedMilliseconds);
}
static void Main()
{
    Execute(1, false); // for JIT
    Execute(1000000, true); // for measuring
}
Run Code Online (Sandbox Code Playgroud)

基本上:“无法重现”。另外:为了完整性, no:.Count不会计算所有项目(它已经知道计数),编译器也不会添加任何神奇的自动缓存代码(注意:有一些有限的例子;例如,JIT可以删除向量for上循环的边界检查)。