【问题标题】:Iterating speed and data type迭代速度和数据类型
【发布时间】:2013-05-05 20:19:19
【问题描述】:

数组(或列表、链接列表、字典等)的迭代速度是否取决于数据类型?

示例: 一个由 10 个布尔值组成的数组与一个由 10 个整数组成的数组?

【问题讨论】:

  • 试试看,你会发现。
  • @EricLippert 至少你应该告诉他如何尝试?你认为他是一名高级程序员(或者至少有 3-4 年的专业编程经验),不是吗?
  • @KingKing:如果 OP 无法编写一个遍历两个数组的程序,那么他们无法编写的程序有多快又有什么关系呢?他们写不出来。 (也就是说,编写正确的基准可能很困难;这将是我即将发表的一系列文章的主题;请查看我的博客了解详细信息。)
  • 我关于为什么你不应该在 StackOverflow 上问这个问题的标准咆哮在这里:ericlippert.com/2012/12/17/performance-rant
  • @KingKing:是的。现在,话虽如此,这里的关键教训是只有当用户能够注意到好性能和坏性能之间的差异时,性能差异才会有意义。所以,双向编写程序,双向运行——你能分辨出区别吗?如果没有,那么哪个更快并不重要!哪个隐形独角兽更漂亮并不重要。 ]

标签: c# performance iteration


【解决方案1】:

是的,数据类型很重要。它与迭代无关;它与数据类型有关。

值类型

int 的长度为 4 个字节。 decimal 的长度为 16 个字节。所以decimalint 的4 倍。每次从数组中检索一个值时,都会复制该值。如果是decimal,则复制 16 个字节。 (在引用类型的情况下,引用被复制,通常为 4 或 8 个字节)。复制更多字节只会减慢迭代速度。

拳击

如果你遍历一个集合,你也有可能改变类型。例如:

foreach(object o in new int[] { 1,2,3 })
     ....

这会将每个int 装箱到object。这需要时间。这与迭代无关,它与您正在装箱这一事实有关。

选角

最后一个例子:还有数组在你必须转换的地方:

foreach(Person p in new object[] { ... })
     ....

铸造也需要额外的时间。

编辑

一些时间测量来支持我的主张:

【讨论】:

  • 实际上,当您拥有List<Decimal> 时,数据复制并不重要,因为数据会按原样 呈现给迭代器。避免了拆箱,并且装箱/拆箱没有真正的损失。这就是应避免使用 ArrayList 的原因,因为它们以这种方式运行。见MSDN Remark on this
  • @Chibueze:在我的文字中我说小数是装箱还是拆箱?
  • +1 获取屏幕截图。您的关键点是数据在迭代期间被复制,并且该数据的复制将产生时间变化。此外,您正在强制装箱和拆箱,这不是问题所在。问题是专门询问有关遍历数组或集合的问题。这不是 List 等集合在内部实现的方式。
  • 我不是在谈论集合在内部是如何工作的。我说的是你如何遍历一个集合。是的,我在我的例子中强加了这种情况,因为我多次看到这种迭代(装箱、拆箱、铸造等)
  • 实际上强制装箱和拆箱是没有必要的,在现实生活中应该建议不要这样做。
【解决方案2】:

如果需要,请运行下面的代码,但这里有一个快速比较。 它所做的只是遍历数组/列表,并将临时变量设置为该索引中的值。

请注意,现在运行时 Int 性能以某种方式受到了影响...不知道为什么...但重复运行时也会发生这种情况...

    namespace Iterating_types
    {
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
class Program
    {
        static void Main(string[] args)
        {
            Thread.CurrentThread.Priority = ThreadPriority.Highest;
            Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;

            Stopwatch watch = new Stopwatch();
            int UPPER = 1000000;
            int[] int_arr = Enumerable.Range(1, UPPER).ToArray();
            List<int> int_list = Enumerable.Range(1, UPPER).ToList();

            Int32[] int32_arr = Enumerable.Range(1, UPPER).ToArray();

            Int64[] int64_arr = new Int64[UPPER]; 

            IntObject[] intobject_arr = new IntObject[UPPER];
            List<IntObject> intobject_list = new List<IntObject>();

            string[] string_arr = new string[UPPER];
            List<string> string_list = new List<string>();

            bool[] bool_arr = new bool[UPPER];
            Boolean[] boolean_arr = new Boolean[UPPER];
            List<bool> bool_list = new List<bool>();
            List<Boolean> boolean_list = new List<Boolean>();
            // Initializing some of the above
            for (int i = 0; i < UPPER; i++)
            {
                int64_arr[i] = (Int64) i;
                string_arr[i] = i.ToString();
                string_list.Add(i.ToString());
                intobject_arr[i] = new IntObject(i);
                intobject_list.Add(new IntObject(i));
                bool_arr[i] = (i%2 ==0);
                boolean_arr[i] = (i%2 ==0);
                bool_arr[i] = (i%2 ==0);
                bool_list.Add(i%2 ==0);

                boolean_list.Add(i%2 == 0);
            }

            Console.WriteLine("Iterations: {0}{1}", UPPER, Environment.NewLine);
            Console.WriteLine("Thread priority: {0}", Thread.CurrentThread.Priority);
            Console.WriteLine("Process priority: {0}", Process.GetCurrentProcess().PriorityClass);

            Console.WriteLine("\n\rArrays:\t----------------------------------------------");

            bool b;
            b = bool_arr[1];
            watch.Start();
            for (int i = 0; i < UPPER; i++)
            {
                b = bool_arr[i];
            }
            watch.Stop();
            Console.WriteLine("Type: bool\tStructure: Array\tticks: {0}\tMiliSeconds:{1}", watch.ElapsedTicks, watch.ElapsedMilliseconds);

            watch.Start();
            for (int i = 0; i < UPPER; i++)
            {
                b = boolean_arr[i];
            }
            watch.Stop();
            Console.WriteLine("Type: Boolean\tStructure: Array\tticks: {0}\tMiliSeconds:{1}", watch.ElapsedTicks, watch.ElapsedMilliseconds);

            int temp_int;
            temp_int = int_arr[1];
            watch.Start();
            for (int i = 0; i < UPPER; i++)
            {
                temp_int = int_arr[i];
            }
            watch.Stop();
            Console.WriteLine("Type: Int\tStructure: Array\tticks: {0}\tMiliSeconds:{1}", watch.ElapsedTicks, watch.ElapsedMilliseconds);

            Int32 temp_int32 ;
            temp_int32 = int32_arr[1];
            watch.Reset();
            watch.Start();
            for (int i = 0; i < UPPER; i++)
            {
                temp_int32 = int32_arr[i];
            }
            watch.Stop();
            Console.WriteLine("Type: Int32\tStructure: Array\tticks: {0}\tMiliSeconds:{1}", watch.ElapsedTicks, watch.ElapsedMilliseconds);

            Int64 temp_int64 ;
            temp_int64 = int64_arr[1];
            watch.Reset();
            watch.Start();
            for (int i = 0; i < UPPER; i++)
            {
                temp_int64 = int64_arr[i];
            }
            watch.Stop();
            Console.WriteLine("Type: Int64\tStructure: Array\tticks: {0}\tMiliSeconds:{1}", watch.ElapsedTicks, watch.ElapsedMilliseconds);

            string s ;
            s = string_arr[1];
            watch.Reset();
            watch.Start();
            for (int i = 0; i < UPPER; i++)
            {
                s = string_arr[i];
            }
            watch.Stop();
            Console.WriteLine("Type: string\tStructure: Array\tticks: {0}\tMiliSeconds:{1}", watch.ElapsedTicks, watch.ElapsedMilliseconds);

            temp_int = intobject_arr[1].IntValue;
            watch.Reset();
            watch.Start();
            for (int i = 0; i < UPPER; i++)
            {
                temp_int = intobject_arr[i].IntValue;
            }
            watch.Stop();
            Console.WriteLine("Type: IntObject\tStructure: Array\tticks: {0}\tMiliSeconds:{1}", watch.ElapsedTicks, watch.ElapsedMilliseconds);

            Console.WriteLine("\n\rLists:\t----------------------------------------------");

            watch.Reset();
            watch.Start();
            foreach (var val in bool_list)
            {
                b = val;
            }
            watch.Stop();
            Console.WriteLine("Type: bool\tStructure: List\t\tticks: {0}\tMiliSeconds:{1}", watch.ElapsedTicks, watch.ElapsedMilliseconds);

            watch.Reset();
            watch.Start();
            foreach (var val in boolean_list)
            {
                b = val;
            }
            watch.Stop();
            Console.WriteLine("Type: Boolean\tStructure: List\t\tticks: {0}\tMiliSeconds:{1}", watch.ElapsedTicks, watch.ElapsedMilliseconds);

            temp_int = int_list.First();
            watch.Reset();
            watch.Start();
            foreach (var val in int_list)
            {
                temp_int = val;
            }
            watch.Stop();
            Console.WriteLine("Type: Int\tStructure: List\t\tticks: {0}\tMiliSeconds:{1}", watch.ElapsedTicks, watch.ElapsedMilliseconds);

            s = string_list.First();
            watch.Reset();
            watch.Start();
            foreach (var val in string_list)
            {
                s = val;
            }
            watch.Stop();
            Console.WriteLine("Type: string\tStructure: List\t\tticks: {0}\tMiliSeconds:{1}", watch.ElapsedTicks, watch.ElapsedMilliseconds);

            temp_int = intobject_list.First().IntValue;
            watch.Reset();
            watch.Start();
            foreach (var val in intobject_list)
            {
                temp_int = val.IntValue;
            }
            watch.Stop();
            Console.WriteLine("Type: IntObject\tStructure: List\t\tticks: {0}\tMiliSeconds:{1}", watch.ElapsedTicks, watch.ElapsedMilliseconds);

            Console.WriteLine();
            Console.WriteLine("Hit any key to exit.");
            Console.ReadKey();


        }
    }

    class IntObject
    {
        public int IntValue { get; set; }

        public IntObject ()
        {
            IntValue = 0;
        }

        public IntObject(int i)
        {
            IntValue = i;
        }
    }
}

【讨论】:

  • 衡量性能是艺术。看起来 Int64 比 Int32 花费更多时间,但与 int 一样多。这是不正确的。 int 是 Int32 的别名,应该同样快。
  • 嗯,看起来确实如此 :) 我更新了代码和屏幕截图,数字大致相同。请注意,一毫秒有 10,000 个滴答声,但我在输出中看到的数字似乎不是很精确。即便如此,这是一百万次迭代,所以最后,这并不重要。
  • 测试的迭代次数越多,您排除的古怪事物就越多。下一次的一点建议:1)总是先运行一个测试(一次迭代),没有时间测量。在此迭代期间,可能会加载一些程序集(这可能会减慢实际测试的速度)。 2)将你的进程和线程优先级设置为最高值,尽可能防止上下文切换,阻碍测试结果。
  • 或者您可以对数据进行一些统计处理,而不是仅仅依赖平均值或大迭代的结果,例如计算置信区间。
  • Martin: 听从了建议,不能说结果不同 :) Caian : 这就是为什么小鱼不应该和鲨鱼一起游泳的原因......自从 Uni 有一段时间了,我们没有' t 做置信区间(或者至少,我不记得了 :)
【解决方案3】:

简单的答案是引用类型是值类型是否

这是因为,.NET 泛型的实现方式是boxing/unboxing is avoided when using Value Types,尽管不在 ArrayLists 中。例如,List&lt;int&gt; 会将数组整数直接存储为堆上的整数而不是对象。在引用类型的情况下,例如List&lt;string&gt;, List&lt;person&gt; 但是,从对象到数据类型的转换/强制转换会有一点时间损失。

comparison between HashSet and List using strings and objects

当您进行大量迭代时,决定在ListLinkedListDictionaryHashSet等之间使用哪一个主要是了解如何它们被存储,以及它们的运行时复杂性。下面是一些 .NET 泛型的实现和渐近索引/迭代时间复杂度的列表:

.NET 泛型的内部实现/渐近时间复杂度


+------------------+------------------ ---------+-------------+--------------+ | | |项目[i] | | |姓名 |内部实现|------------+------------|迭代 | | | |平均案例 |最坏情况 | | +------------------+------------------ ---------+------------+------------+-------------+ |列表 |数组 | O(1) | O(1) | O(n) | |链表 |双向链表 | O(n) | O(n) | O(n) | |字典 |带有指向另一个数组的链接的哈希表 | O(1) | O(n) | O(n) | |哈希集 |带有指向另一个数组的链接的哈希表 | O(1) | O(n) | O(n) | |排序字典 |红黑树 | O(log n) | O(log n) | O(n) | |排序列表 |数组 | O(1) | O(n) | O(n) | |排序集 |红黑树 | O(n) | O(n) | O(n) | +------------------+------------------ ---------+------------+------------+-------------+

总而言之,可以根据它们的时间复杂度确定迭代这些数据类型的最可能速度。就快速查找项目而言,ListSortedListDictionaryHashSet 将始终胜过其他人,但如果您要去,则不建议使用 ListSortedList处理大量项目,然后将 DictionaryHashSet 置于大型列表的优势(性能最重要)。

参考资料:

  1. Runtime Complexity of .NET Generic Collection
  2. Comparative Analysis of List, HashSet and SortedSet
  3. Time complexity overview: Dictionary classes
  4. Generic Collections in C# - Time complexity overview
  5. Algorithms: Big-Oh Notation
  6. Sorted Dictionary(Tkey, Tvalue) - MSDN
  7. List(T) - MSDN

词汇表:

  1. Red-black tree
  2. Boxing and Unboxing

【讨论】:

  • 一个美丽的答案,但我不认为你回答了真正的问题。首先,我认为您的第一行应该是:“引用类型为否,值类型为是”,因为简而言之,问题是:“通过数组的迭代速度是否取决于数据类型”。他并没有询问收藏类型的差异。此外,您将Item[i]Find(i) 组合在一起,它们是完全不同的操作。在数组中,Item[i] 是 O(1) 操作,但 Find(i) 是 O(n) 操作。
  • 没有。实际上,它对引用类型很重要,但对值类型不重要,反之亦然。见MSDN Remarks。至于 Item[i]/Item 查找标题,可能它是模棱两可的,但它并不意味着与您所指的 List.Find( match) 相同。
  • 请回复我评论的前半部分。
  • 你的意思是额外的细节是不必要的还是什么?
  • 另外,您在回答中确实提到强制转换需要时间,那么为什么要对引用类型说 NO?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-22
  • 2020-07-12
  • 1970-01-01
  • 1970-01-01
  • 2012-08-26
相关资源
最近更新 更多