8

这些枚举中的一个比另一个更快还是大致相同?(C# 中的示例)

情况1:

Dictionary<string, object> valuesDict;

// valuesDict loaded with thousands of objects

foreach (object value in valuesDict.Values) { /* process */ }

案例二:

List<object> valuesList;

// valuesList loaded with thousands of objects

foreach (object value in valuesList) { /* process */ }

更新:

背景:

字典将有利于其他地方的键控搜索(与遍历列表相反),但如果遍历字典比遍历列表慢得多,则好处会减少。

更新:听取许多人的建议,我已经完成了自己的测试。

首先,这些是结果。以下是程序。

迭代整个集合字典:78 Keyd:131 List:76

关键字搜索集合字典:178 关键字:194 列表:142800

using System;
using System.Linq;

namespace IterateCollections
{
    public class Data
    {
        public string Id;
        public string Text;
    }

    public class KeyedData : System.Collections.ObjectModel.KeyedCollection<string, Data>
    {
        protected override string GetKeyForItem(Data item)
        {
            return item.Id;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var dict = new System.Collections.Generic.Dictionary<string, Data>();
            var list = new System.Collections.Generic.List<Data>();
            var keyd = new KeyedData();

            for (int i = 0; i < 10000; i++)
            {
                string s = i.ToString();
                var d = new Data { Id = s, Text = s };
                dict.Add(d.Id, d);
                list.Add(d);
                keyd.Add(d);
            }

            var sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            for (int r = 0; r < 1000; r++)
            {
                foreach (Data d in dict.Values)
                {
                    if (null == d) throw new ApplicationException();
                }
            }
            sw.Stop();
            var dictTime = sw.ElapsedMilliseconds;

            sw.Reset();
            sw.Start();
            for (int r = 0; r < 1000; r++)
            {
                foreach (Data d in keyd)
                {
                    if (null == d) throw new ApplicationException();
                }
            }
            sw.Stop();
            var keydTime = sw.ElapsedMilliseconds;

            sw.Reset();
            sw.Start();
            for (int r = 0; r < 1000; r++)
            {
                foreach (Data d in list)
                {
                    if (null == d) throw new ApplicationException();
                }
            }
            sw.Stop();
            var listTime = sw.ElapsedMilliseconds;

            Console.WriteLine("Iterate whole collection");
            Console.WriteLine("Dict: " + dictTime);
            Console.WriteLine("Keyd: " + keydTime);
            Console.WriteLine("List: " + listTime);

            sw.Reset();
            sw.Start();
            for (int r = 0; r < 1000; r++)
            {
                for (int i = 0; i < 10000; i += 10)
                {
                    string s = i.ToString();
                    Data d = dict[s];
                    if (null == d) throw new ApplicationException();
                }
            }
            sw.Stop();
            dictTime = sw.ElapsedMilliseconds;

            sw.Reset();
            sw.Start();
            for (int r = 0; r < 1000; r++)
            {
                for (int i = 0; i < 10000; i += 10)
                {
                    string s = i.ToString();
                    Data d = keyd[s];
                    if (null == d) throw new ApplicationException();
                }
            }
            sw.Stop();
            keydTime = sw.ElapsedMilliseconds;

            sw.Reset();
            sw.Start();
            for (int r = 0; r < 10; r++)
            {
                for (int i = 0; i < 10000; i += 10)
                {
                    string s = i.ToString();
                    Data d = list.FirstOrDefault(item => item.Id == s);
                    if (null == d) throw new ApplicationException();
                }
            }
            sw.Stop();
            listTime = sw.ElapsedMilliseconds * 100;

            Console.WriteLine("Keyed search collection");
            Console.WriteLine("Dict: " + dictTime);
            Console.WriteLine("Keyd: " + keydTime);
            Console.WriteLine("List: " + listTime);

        }
    }

}

更新:

@Blam 建议的 Dictionary 与 KeyedCollection 的比较。

最快的方法是遍历 KeyedCollection 项数组。

但是请注意,在不转换为数组的情况下,遍历字典值比遍历 KeyedCollection 更快。

请注意,遍历字典值比遍历字典集合要快得多。

 Iterate 1,000 times over collection of 10,000 items
   Dictionary Pair:   519 ms
 Dictionary Values:    95 ms
  Dict Val ToArray:    92 ms
   KeyedCollection:   141 ms
   KeyedC. ToArray:    17 ms

计时来自 Windows 控制台应用程序(发布版本)。这是源代码:

using System;
using System.Collections.Generic;
using System.Linq;

namespace IterateCollections
{
    public class GUIDkeyCollection : System.Collections.ObjectModel.KeyedCollection<Guid, GUIDkey>
    {
        // This parameterless constructor calls the base class constructor 
        // that specifies a dictionary threshold of 0, so that the internal 
        // dictionary is created as soon as an item is added to the  
        // collection. 
        // 
        public GUIDkeyCollection() : base() { }

        // This is the only method that absolutely must be overridden, 
        // because without it the KeyedCollection cannot extract the 
        // keys from the items.  
        // 
        protected override Guid GetKeyForItem(GUIDkey item)
        {
            // In this example, the key is the part number. 
            return item.Key;
        }

        public GUIDkey[] ToArray()
        {
            return Items.ToArray();
        }

        //[Obsolete("Iterate using .ToArray()", true)]
        //public new IEnumerator GetEnumerator()
        //{
        //    throw new NotImplementedException("Iterate using .ToArray()");
        //}
    }
    public class GUIDkey : Object
    {
        private Guid key;
        public Guid Key
        {
            get
            {
                return key;
            }
        }
        public override bool Equals(Object obj)
        {
            //Check for null and compare run-time types.
            if (obj == null || !(obj is GUIDkey)) return false;
            GUIDkey item = (GUIDkey)obj;
            return (Key == item.Key);
        }
        public override int GetHashCode() { return Key.GetHashCode(); }
        public GUIDkey(Guid guid)
        {
            key = guid;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            const int itemCount = 10000;
            const int repetitions = 1000;
            const string resultFormat = "{0,18}: {1,5:D} ms";

            Console.WriteLine("Iterate {0:N0} times over collection of {1:N0} items", repetitions, itemCount);

            var dict = new Dictionary<Guid, GUIDkey>();
            var keyd = new GUIDkeyCollection();

            for (int i = 0; i < itemCount; i++)
            {
                var d = new GUIDkey(Guid.NewGuid());
                dict.Add(d.Key, d);
                keyd.Add(d);
            }

            var sw = new System.Diagnostics.Stopwatch();
            long time;

            sw.Reset();
            sw.Start();
            for (int r = 0; r < repetitions; r++)
            {
                foreach (KeyValuePair<Guid, GUIDkey> w in dict)
                {
                    if (null == w.Value) throw new ApplicationException();
                }
            }
            sw.Stop();
            time = sw.ElapsedMilliseconds;
            Console.WriteLine(resultFormat, "Dictionary Pair", time);

            sw.Reset();
            sw.Start();
            for (int r = 0; r < repetitions; r++)
            {
                foreach (GUIDkey d in dict.Values)
                {
                    if (null == d) throw new ApplicationException();
                }
            }
            sw.Stop();
            time = sw.ElapsedMilliseconds;
            Console.WriteLine(resultFormat, "Dictionary Values", time);

            sw.Reset();
            sw.Start();
            for (int r = 0; r < repetitions; r++)
            {
                foreach (GUIDkey d in dict.Values.ToArray())
                {
                    if (null == d) throw new ApplicationException();
                }
            }
            sw.Stop();
            time = sw.ElapsedMilliseconds;
            Console.WriteLine(resultFormat, "Dict Val ToArray", time);

            sw.Reset();
            sw.Start();
            for (int r = 0; r < repetitions; r++)
            {
                foreach (GUIDkey d in keyd)
                {
                    if (null == d) throw new ApplicationException();
                }
            }
            sw.Stop();
            time = sw.ElapsedMilliseconds;
            Console.WriteLine(resultFormat, "KeyedCollection", time);

            sw.Reset();
            sw.Start();
            for (int r = 0; r < repetitions; r++)
            {
                foreach (GUIDkey d in keyd.ToArray())
                {
                    if (null == d) throw new ApplicationException();
                }
            }
            sw.Stop();
            time = sw.ElapsedMilliseconds;
            Console.WriteLine(resultFormat, "KeyedC. ToArray", time);
        }
    }

}
4

5 回答 5

12

用秒表检查相对容易:

var d = new Dictionary<string, object>();
var s = new List<object>();
for (int i =0 ; i != 10000000 ; i++) {
    d.Add(""+i, i);
    s.Add(i);
}
var sw = new Stopwatch();
sw.Start();
foreach(object o in d.Values) {
    if (o == null) throw new ApplicationException();
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
foreach (object o in s) {
    if (o == null) throw new ApplicationException();
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

这将打印彼此相当接近的数字:

Dict List
---- ----
 136  107
 139  108
 136  108

List总是获胜,但考虑到两种数据结构的相对复杂性,利润并不像人们预期的那么大。

于 2013-04-09T14:39:00.923 回答
4

差不多是同一时间。一旦您的流程包含任何代码,它肯定不会引起注意。

但是你为什么要听网上的随机人呢?测试它!

秒表类可能很有用。

于 2013-04-09T14:28:07.543 回答
4

如果你想按键查找,那么字典。
字典键查找非常快,因为这是它的设计目的。

foreach 的差异很小。

如果键也是一个属性,那么考虑 KeyedCollection
KeyedCollection 类

为其键嵌入值中的集合提供抽象基类。

正如 Servy 评论的那样,选择具有您需要的功能的集合
。NET 有很多集合。
System.Collections 命名空间

如果您的对象有一个可以表示为 Int32 的自然键,那么请考虑 OverRide GetHashCode()。

如果您的对象具有 GUID 的自然键,请考虑 KeyedCollection 和 OverRide GetHashCode 和 Equals

对于非关键属性的搜索,请考虑使用 LINQ 而不是 ForEach 休息;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Diagnostics;

namespace IntIntKeyedCollection
{
    class Program
    {
        static void Main(string[] args)
        {

            Guid findGUID = Guid.NewGuid();
            GUIDkeyCollection gUIDkeyCollection = new GUIDkeyCollection();
            gUIDkeyCollection.Add(new GUIDkey(findGUID));
            gUIDkeyCollection.Add(new GUIDkey(Guid.NewGuid()));
            gUIDkeyCollection.Add(new GUIDkey(Guid.NewGuid()));
            GUIDkey findGUIDkey = gUIDkeyCollection[findGUID];  // lookup by key (behaves like a dict)
            Console.WriteLine(findGUIDkey.Key);
            Console.WriteLine(findGUIDkey.GetHashCode());
            Console.WriteLine(findGUID);
            Console.WriteLine(findGUID.GetHashCode());
            Console.ReadLine();
        }
        public class GUIDkeyCollection : KeyedCollection<Guid, GUIDkey>
        {
            // This parameterless constructor calls the base class constructor 
            // that specifies a dictionary threshold of 0, so that the internal 
            // dictionary is created as soon as an item is added to the  
            // collection. 
            // 
            public GUIDkeyCollection() : base() { }

            // This is the only method that absolutely must be overridden, 
            // because without it the KeyedCollection cannot extract the 
            // keys from the items.  
            // 
            protected override Guid GetKeyForItem(GUIDkey item)
            {
                // In this example, the key is the part number. 
                return item.Key;
            }
        }
        public class GUIDkey : Object
        {
            private Guid key;
            public Guid Key
            {
                get
                {
                    return key;
                }
            }
            public override bool Equals(Object obj)
            {
                //Check for null and compare run-time types.
                if (obj == null || !(obj is GUIDkey)) return false;
                GUIDkey item = (GUIDkey)obj;
                return (Key == item.Key);
            }
            public override int GetHashCode() { return Key.GetHashCode(); }
            public GUIDkey(Guid guid)
            {
                key = guid;
            }
        }
    }
}
于 2013-04-09T14:31:12.903 回答
2

这是你的测试:

class speedtest
{
    static void Main(string[] args)
    {
        int size = 10000000;
        Dictionary<string, object> valuesDict = new Dictionary<string, object>();
        List<object> valuesList = new List<object>();

        for (int i = 0; i < size; i++)
        {
            valuesDict.Add(i.ToString(), i);
            valuesList.Add(i);
        }
        // valuesDict loaded with thousands of objects

        Stopwatch s = new Stopwatch();
        s.Start();
        foreach (object value in valuesDict.Values) { /* process */ }
        s.Stop();

        Stopwatch s2 = new Stopwatch();
        s2.Start();
        foreach (object value in valuesList) { /* process */ }
        s.Stop();
        Console.WriteLine("Size {0}, Dictonary {1}ms, List {2}ms",size,s.ElapsedMilliseconds,s2.ElapsedMilliseconds);

        Console.ReadLine();
    }
}

Outputs:
Size 10000000, Dictonary 73ms, List 63ms

但是,您还应该测试您的字典中是否有散列冲突。他们可以给你不同的结果。

在实际应用程序的情况下,您必须考虑创建、访问结构和内存占用所花费的时间。

于 2013-04-09T14:41:16.277 回答
2

正如其他人所说,过早优化yadda yadda。为正确的场景使用正确的集合,只有在出现问题时才担心速度。

无论如何,唯一知道的方法就是测量。我做了一个快速而肮脏的测试,用 30,000 个对象填充字典和列表,然后将它们迭代 10,000 次。结果是:

字典:2683ms
列表:1955ms

因此,无论出于何种原因,Dictionary.Values 的枚举似乎都会稍慢一些。

这是代码:

void Main()
{
    int i = 0;

    var dict = new Dictionary<string, object>();
    var list = new List<object>();

    for (i = 0; i < 30000; i++)
    {
        var foo = new Foo();
        dict.Add(i.ToString(), foo);
        list.Add(foo);
    }

    var dictWatch = new Stopwatch();

    dictWatch.Start();

    for (i = 0; i < 10000; i++)
    {
        foreach (var foo in dict.Values) {}
    }

    dictWatch.Stop();
    Console.WriteLine("Dictionary: " + dictWatch.ElapsedMilliseconds);

    var listWatch = new Stopwatch();
    listWatch.Start();

    for (i = 0; i < 10000; i++)
    {
        foreach (var foo in list) {}
    }

    listWatch.Stop();

    Console.WriteLine("List: " + listWatch.ElapsedMilliseconds);
}

class Foo {}
于 2013-04-09T14:45:36.783 回答