不同字符串字典的基准测试显示正常的字典更快,我不知道为什么

时间:2010-12-14 21:05:33

标签: c# .net performance dictionary benchmarking

我正在对几个String dicctionaries做一些基准测试,因为我想知道如何执行不同的配置,我很惊讶普通的Dictionary更快。可能是因为我错过了一些概念或者我做错了什么,但这些都是我得到的结果:

Collection:             2147482 items.
Random Keys:            1000 keys.

Normal dicctionary
        Add 1000 items:         573ms.
        Get 1000 keys:          0ms.

Normal Dicctionary with OrdinalIgnoreCase comparer
        Add 1000 items:         642ms.
        Get 1000 keys:          0ms.

Normal Dicctionary with InvariantCultureIgnoreCase comparer
        Add 1000 items:         1661ms.
        Get 1000 keys:          0ms.

Sorted dicctionary
        Add 1000 items:         11996ms.
        Get 1000 keys:          5ms.

Sorted dicctionary with OrdinalIgnoreCase comparer
        Add 1000 items:         11097ms.
        Get 1000 keys:          5ms.

Sorted dicctionary with InvariantCultureIgnoreCase comparer
        Add 1000 items:         9814ms.
        Get 1000 keys:          5ms.

这是我用来测试它的代码:

    static void Main(string[] args)
    {
        String[] col = GenerateCollectionUnsorted(Int32.MaxValue / 1000).ToArray();
        Int32 len = col.Length;
        Console.WriteLine("Collection:\t\t" + len.ToString() + " items.");

        String[] randomKeys = GetRandomKeys(col, 1000);
        Console.WriteLine("Random Keys:\t\t" + randomKeys.Length.ToString() + " keys.");

        GC.Collect();
        GC.WaitForPendingFinalizers();

        Console.WriteLine();
        Console.WriteLine("Normal dicctionary");
        TestDic(col, randomKeys, new Dictionary<String, String>());
        GC.Collect();
        GC.WaitForPendingFinalizers();

        Console.WriteLine();
        Console.WriteLine("Normal Dicctionary with OrdinalIgnoreCase comparer");
        TestDic(col, randomKeys, new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase));
        GC.Collect();
        GC.WaitForPendingFinalizers();

        Console.WriteLine();
        Console.WriteLine("Normal Dicctionary with InvariantCultureIgnoreCase comparer");
        TestDic(col, randomKeys, new Dictionary<String, String>(StringComparer.InvariantCultureIgnoreCase));
        GC.Collect();
        GC.WaitForPendingFinalizers();

        Console.WriteLine();
        Console.WriteLine("Sorted dicctionary");
        TestDic(col,randomKeys, new SortedDictionary<String,String>());
        GC.Collect(); 
        GC.WaitForPendingFinalizers();

        Console.WriteLine();
        Console.WriteLine("Sorted dicctionary with OrdinalIgnoreCase comparer");
        TestDic(col, randomKeys, new SortedDictionary<String, String>(StringComparer.OrdinalIgnoreCase));
        GC.Collect();
        GC.WaitForPendingFinalizers();

        Console.WriteLine();
        Console.WriteLine("Sorted dicctionary with InvariantCultureIgnoreCase comparer");
        TestDic(col, randomKeys, new SortedDictionary<String, String>(StringComparer.InvariantCultureIgnoreCase));
        GC.Collect();
        GC.WaitForPendingFinalizers();

        Console.WriteLine("\nEnd");
        Console.ReadKey(true);
    }

    static void TestDic(String[] col, String[] randomKeys, IDictionary<String,String> dic)
    {
        Stopwatch crono = new Stopwatch();

        crono.Start();
        foreach (String s in col)
            dic.Add(s, s);
        crono.Stop();

        Console.WriteLine("\tAdd "+randomKeys.Length.ToString()+" items:\t\t" + crono.ElapsedMilliseconds.ToString() + "ms.");

        crono.Reset();
        crono.Start();
        String sv;
        foreach (var rk in randomKeys)
        {
            sv = dic[rk];
            sv.ToString();
        }
        crono.Stop();
        Console.WriteLine("\tGet " + randomKeys.Length.ToString() + " keys:\t\t" + crono.ElapsedMilliseconds.ToString() + "ms.");
    }

    static String[] GetRandomKeys(String[] col, Int32 count)
    {
        Random ran = new Random(DateTime.Now.Millisecond);

        List<Int32> indexSelection = new List<int>();
        List<String> selection = new List<string>();

        Int32 len = col.Length;

        while (indexSelection.Count < count)
        {
            Int32 value = ran.Next(0, len - 1);
            if (!indexSelection.Contains(value))
            {
                indexSelection.Add(value);
                selection.Add(col[value]);
            }
        }
        return selection.ToArray();
    }

    static IEnumerable<String> GenerateCollection(Int32 count)
    {
        for (int i = 0; i < count; i++)
        {
            yield return i.ToString("X").PadLeft(5, '0');
        }
    }

    static IEnumerable<String> GenerateCollectionUnsorted(Int32 amount)
    {
        for (int i = 0; i < amount / 2; i++)
        {
            yield return i.ToString("X").PadLeft(5, '0');
            yield return (amount-i).ToString("X").PadLeft(5, '0');
        }
    }

知道为什么会这样吗?

编辑1:我的理解是,排序的字典会更慢地添加项目,并且因为集合已经排序而更快地获取它们。而且,将字符串与OrdinalIgnoreCase或InvariantCultureIgnoreCase进行比较应该比正常比较更快,因此搜索应该更快。但也许我的理解是完全错误的:D

提前致谢。

编辑2:由于好奇心,我对字符串比较进行了一些测试:

Collection:             2147482 items.
Random Keys:            1000 keys.

CurrentCulture
        LookUp:         158209ms.

CurrentCultureIgnoreCase
        LookUp:         160710ms.

InvariantCulture
        LookUp:         132765ms.

InvariantCultureIgnoreCase
        LookUp:         133409ms.

Ordinal
        LookUp:         36115ms.

OrdinalIgnoreCase
        LookUp:         36329ms.

3 个答案:

答案 0 :(得分:3)

因为只有普通字典和默认比较器实际上是普通字典,所以其他字典都是排序字典。

所以,结果非常一致。

编辑:

修复后,结果会有所不同。 :)

在添加项目时,排序字典的速度较慢,因为它们需要进行排序,但在获取项目时速度并不快。搜索二叉树很快,但搜索常规字典使用的哈希列表更快。当二叉树增长时,将会有更多步骤来定位每个项目,而字典主要通过添加更多桶来增长,因此比较的数量几乎不受影响。

比较字符串Ordinal比常规(CurrentCulture)比较快,OrdinalIgnoreCaseCurrentCultureIgnoreCase快,但不确定OrdinalIgnoreCase是快于CurrentCultureInvariantCulture比较并不比常规比较快,它只是使用不同的文化。顺序比较快得多的原因是它根本不需要为文化环境而烦恼。

顺便说一下,我注意到GetRandomKeys方法中有一个错误。它永远不会选择最后一项,因为你得到一个0和长度为2的随机数。

答案 1 :(得分:3)

来自MSDN documentation on SortedDictionary

  

SortedDictionary<TKey, TValue>泛型类是一个带有O(log n)检索的二叉搜索树,其中n是字典中元素的数量。

来自docs on Dictionary

  

使用其密钥检索值非常快,接近于O(1),因为Dictionary<TKey, TValue>类是作为哈希表实现的。

因此,Dictionary在许多情况下可以胜过SortedDictionary并不奇怪。

答案 2 :(得分:2)

我没有理解为什么你会对结果感到惊讶。

“normal”/ unsorted显然会比排序字典快,因为已排序的字典必须执行额外的操作。

具有额外选项的两个“普通”都需要额外处理。