为什么List.Sort()比TreeView.Sort()快得多?

时间:2016-05-25 18:23:50

标签: c# list treeview

实施例: 我有一个列表和一个包含10000个相同元素的树。

在进行基准测试时,我在排序时间方面有很大差异: List.Sort()(约60毫秒) TreeView.Sort()(约3600毫秒)

我特意将比较器编码为完全相同。

根据下面的代码,有人可以解释时间上的差异吗?

height

4 个答案:

答案 0 :(得分:3)

但是你的比较器并不完全相同。 comp_listIComparer<string>。它的Compare方法接受string个参数,而ToString方法只返回字符串。很可能JIT编译器可以用只返回原始引用的代码替换对string.ToString的所有调用。

实际上,将您的comp_list.Compare方法替换为:

        public int Compare(string xx, string yy)
        {
            return xx.CompareTo(yy);
        }

导致运行时间没有差异。

comp_treeIComparer。传递给它的项目是TreeNode引用。比较器必须在该对象上调用ToString方法,然后必须返回Text属性的值。

简单地说,string.ToStringTreeNode.ToString快得多。

更多信息

实际上,还有更多的事情要发生。我稍微修改了你的代码以便比较并计算它们。事实证明,比较确实需要更长的时间,正如我在上面所推测的那样,但TreeView排序的比较大约是List排序的两倍。我怀疑它也花了很多时间交换东西。这是修改过的代码:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;

namespace treeviewSort
{
    internal class Program
    {
        public class comp_list : IComparer<string>
        {
            public Stopwatch sw = new Stopwatch();
            public int CompareCount = 0;
            public int Compare(string xx, string yy)
            {
                ++CompareCount;
                sw.Start();
                string x = xx.ToString();
                string y = yy.ToString();
                sw.Stop();
                return x.CompareTo(y);
            }
        }

        public class comp_tree : IComparer
        {
            public Stopwatch sw = new Stopwatch();
            public int CompareCount = 0;
            public int Compare(object xx, object yy)
            {
                ++CompareCount;
                sw.Start();
                string x = xx.ToString();
                string y = yy.ToString();
                sw.Stop();
                return x.CompareTo(y);
            }
        }


        private static void Main()
        {
            DoThisTwice();
            DoThisTwice();
            Console.ReadLine();
        }

        private static void DoThisTwice()
        {
            List<string> MyList = new List<string>();
            TreeView tv = new TreeView();

            int Cnt = 10000;
            string s = "";
            Random R = new Random();
            for (int i = 0; i < Cnt; i++)
            {
                s = (R.Next(0, Cnt)).ToString();
                MyList.Add(s);
                tv.Nodes.Add(s);
            }

            Stopwatch t = new Stopwatch();
            t.Start();
            comp_list cmp = new comp_list();
            MyList.Sort(cmp);
            t.Stop();
            Console.WriteLine("SORT_LIST={0}. Comparisons={1}. Compare time={2}", t.ElapsedMilliseconds, cmp.CompareCount, cmp.sw.ElapsedMilliseconds);

            var tvcmp = new comp_tree();
            tv.TreeViewNodeSorter = tvcmp;
            Stopwatch tt = new Stopwatch();
            tt.Start();
            tv.Sort();
            tt.Stop();
            Console.WriteLine("SORT_TREE={0} Comparisons={1}. Compare time={2}", tt.ElapsedMilliseconds, tvcmp.CompareCount, tvcmp.sw.ElapsedMilliseconds);
        }
    }
}

我运行它两次以消除可能由JIT代码引起的任何延迟。第一次通过确保所有内容都已编译,因此下次没有任何此类延迟。在这个简单的代码中,它似乎没什么区别,但总的来说它是一个好主意。实际上,最好在一次测试中运行100次或更多次,但就我们的目的而言,只运行两次就可以了。

结果很有启发性:

SORT_LIST=20. Comparisons=141289. Compare time=3
SORT_TREE=849 Comparisons=242150. Compare time=25
SORT_LIST=19. Comparisons=141090. Compare time=3
SORT_TREE=850 Comparisons=241987. Compare time=25

查看source code for TreeView.Sort可提供更多见解。基本上发生的是设置Sorted标志,并调用RefreshNodes。该方法将Nodes集合中的所有节点复制到一个数组中。然后清除Nodes集合并调用AddRange以将节点添加回Nodes数组。这项工作是在AddInternal方法中完成的。

因此,List.Sort对项目进行排序,TreeView.Sort构建一个空的节点列表,然后将节点一次一个地插入到新列表中。这就是为什么需要这么长时间。

答案 1 :(得分:1)

首先,您的comp_tree课程不应该调用.ToString(),而应该投放到TreeNode并使用其Text属性。

public class comp_tree : IComparer
{
    public int Compare(object xx, object yy)
    {
        string x = ((TreeNode)xx).Text;
        string y = ((TreeNode)yy).Text;
        return x.CompareTo(y);
    }
}

comp_tree.Compare()的调用频率高于comp_list.Compare()

comp_tree :242042
comp_list :144447

所以,我不得不深入研究TreeView.Sort代码来找到问题。 这个代码肯定不是高性能。它首先清除TreeView中的所有TreeNodes,然后再次按顺序添加排序。这需要时间。

List(T).Sort()算法使用插入排序,Heapsort或Quicksort。当然高度优化。有关详细信息,请参阅List.Sort的备注部分。

TreeView.Sort算法使用某种插入排序,但甚至没有稍微优化。当然要慢得多。

答案 2 :(得分:0)

在这个结构中,项目按顺序分配在内存中,排序后必须找到它的位置&#34;另一方面,在内存中,在前一个元素旁边,列表项只引用了下一个元素,但它们不是按顺序排列在内存中。

答案 3 :(得分:0)

在我的电脑上我有这个:

Jim Mischel源代码:

SORT_LIST=84. Comparisons=178337. Compare time=20
SORT_TREE=3283 Comparisons=242048. Compare time=123
SORT_LIST=79. Comparisons=173581. Compare time=19
SORT_TREE=3282 Comparisons=242100. Compare time=121

SORT_LIST=81. Comparisons=174326. Compare time=19
SORT_TREE=3013 Comparisons=242095. Compare time=119
SORT_LIST=76. Comparisons=172590. Compare time=19
SORT_TREE=3075 Comparisons=241952. Compare time=125

SORT_LIST=82. Comparisons=173555. Compare time=19
SORT_TREE=4012 Comparisons=242104. Compare time=121
SORT_LIST=82. Comparisons=171671. Compare time=22
SORT_TREE=3987 Comparisons=242045. Compare time=136

安迪源纠正:

This variant with
public class comp_tree : IComparer
{
public Stopwatch sw = new Stopwatch();
public int CompareCount = 0;
public int Compare(object xx, object yy)
{
    ++CompareCount;
    sw.Start();
    string x = ((TreeNode)xx).Text;
    string y = ((TreeNode)yy).Text;
    sw.Stop();
    return x.CompareTo(y);
}
}


SORT_LIST=82. Comparisons=176456. Compare time=19
SORT_TREE=3263 Comparisons=242053. Compare time=36
SORT_LIST=79. Comparisons=181171. Compare time=19
SORT_TREE=3094 Comparisons=241980. Compare time=36

SORT_LIST=84. Comparisons=179307. Compare time=20
SORT_TREE=3064 Comparisons=242165. Compare time=37
SORT_LIST=77. Comparisons=173915. Compare time=19
SORT_TREE=3313 Comparisons=242022. Compare time=36

SORT_LIST=87. Comparisons=186245. Compare time=21
SORT_TREE=3214 Comparisons=241970. Compare time=37
SORT_LIST=78. Comparisons=176906. Compare time=19
SORT_TREE=3349 Comparisons=242058. Compare time=36

如果treeview包含100000 elem,那么.Sort我等不及!!!!