代码之家  ›  专栏  ›  技术社区  ›  Anindya Chatterjee

在.NET4中这种巨大的性能差异背后的原因是什么

  •  18
  • Anindya Chatterjee  · 技术社区  · 15 年前

    我只是在研究红黑树。我知道.NET4.0中的SortedSet类使用红黑树。所以我使用Reflector将这部分原样去掉,并创建了一个RedBlackTree类。现在我在这个RedBlackTree上运行一些perf测试,SortedSet插入40000个顺序积分值(从0到39999),我惊讶地发现perf有巨大的差异,如下所示:

     RBTree    took 9.27208   sec to insert 40000 values 
     SortedSet took 0.0253097 sec to insert 40000 values
    

    背后的原因是什么?顺便说一句,我只在版本配置中运行了测试,下面是小测试代码

                var stopWatch = new Stopwatch();
                var rbT = new RedBlackTree<int>();      
            stopWatch = new Stopwatch();
            stopWatch.Start();
            for (int i = 0; i < 40000; i++) {
                rbT.Add(i);
            }
            stopWatch.Stop();
            Console.WriteLine(stopWatch.Elapsed);
    
            var ss = new SortedSet<int>();
            stopWatch = new Stopwatch();
            stopWatch.Start();
            for (int i = 0; i < 40000; i++) {
                ss.Add(i);
            }
            stopWatch.Stop();
            Console.WriteLine(stopWatch.Elapsed);
    

    我想分享我提取的RBTree代码,这样你也可以运行诊断

    public class Node<T>
        {
            public Node(){}
    
            public Node(T value)
            {
                Item = value;
            }       
    
            public Node(T value, bool isRed)
            {
                Item = value;
                IsRed = isRed;
            }
    
            public T Item;
            public Node<T> Left;
            public Node<T> Right;
            public Node<T> Parent;
            public bool IsRed;
        }
    
        public class RedBlackTree<T>
        {
            public RedBlackTree(){} 
    
            public Node<T> root;
            int count, version; 
            Comparer<T> comparer = Comparer<T>.Default;     
    
            public void Add(T item)
            {
                if (this.root == null)
                {
                    this.root = new Node<T>(item, false);
                    this.count = 1;
                    this.version++;
                    return;
                }
    
                Node<T> root = this.root;
                Node<T> node = null;
                Node<T> grandParent = null;
                Node<T> greatGrandParent = null;
                this.version++;
    
                int num = 0;
                while (root != null)
                {
                    num = this.comparer.Compare(item, root.Item);
                    if (num == 0)
                    {
                        this.root.IsRed = false;
                        return;
                    }
                    if (Is4Node(root))
                    {
                        Split4Node(root);
                        if (IsRed(node))
                        {
                            this.InsertionBalance(root, ref node, grandParent, greatGrandParent);
                        }
                    }
                    greatGrandParent = grandParent;
                    grandParent = node;
                    node = root;
                    root = (num < 0) ? root.Left : root.Right;
                }
                Node<T> current = new Node<T>(item);
                if (num > 0)
                {
                    node.Right = current;
                }
                else
                {
                    node.Left = current;
                }
                if (node.IsRed)
                {
                    this.InsertionBalance(current, ref node, grandParent, greatGrandParent);
                }
                this.root.IsRed = false;
                this.count++;
            }
    
    
            private static bool IsRed(Node<T> node)
            {
                return ((node != null) && node.IsRed);
            }
    
            private static bool Is4Node(Node<T> node)
            {
                return (IsRed(node.Left) && IsRed(node.Right));
            }
    
            private static void Split4Node(Node<T> node)
            {
                node.IsRed = true;
                node.Left.IsRed = false;
                node.Right.IsRed = false;
            }
    
            private void InsertionBalance(Node<T> current, ref Node<T> parent, Node<T> grandParent, Node<T> greatGrandParent)
            {
                Node<T> node;
                bool flag = grandParent.Right == parent;
                bool flag2 = parent.Right == current;
                if (flag == flag2)
                {
                    node = flag2 ? RotateLeft(grandParent) : RotateRight(grandParent);
                }
                else
                {
                    node = flag2 ? RotateLeftRight(grandParent) : RotateRightLeft(grandParent);
                    parent = greatGrandParent;
                }
                grandParent.IsRed = true;
                node.IsRed = false;
                ReplaceChildOfNodeOrRoot(greatGrandParent, grandParent, node);
            }
    
            private static Node<T> RotateLeft(Node<T> node)
            {
                Node<T> right = node.Right;
                node.Right = right.Left;
                right.Left = node;
                return right;
            }
    
            private static Node<T> RotateRight(Node<T> node)
            {
                Node<T> left = node.Left;
                node.Left = left.Right;
                left.Right = node;
                return left;
            }
    
            private static Node<T> RotateLeftRight(Node<T> node)
            {
                Node<T> left = node.Left;
                Node<T> right = left.Right;
                node.Left = right.Right;
                right.Right = node;
                left.Right = right.Left;
                right.Left = left;
                return right;
            }
    
            private static Node<T> RotateRightLeft(Node<T> node)
            {
                Node<T> right = node.Right;
                Node<T> left = right.Left;
                node.Right = left.Left;
                left.Left = node;
                right.Left = left.Right;
                left.Right = right;
                return left;
            }
    
            private void ReplaceChildOfNodeOrRoot(Node<T> parent, Node<T> child, Node<T> newChild)
            {
                if (parent != null)
                {
                    if (parent.Left == child)
                    {
                        parent.Left = newChild;
                    }
                    else
                    {
                        parent.Right = newChild;
                    }
                }
                else
                {
                    this.root = newChild;
                }
            }
        }
    

    编辑


    我在其他一些数据结构上运行了相同的诊断(有些是我创建的,有些是从.NETFramework**),下面是有趣的结果

    *AATree                 00:00:00.0309294
    *AVLTree                00:00:00.0129743
    **SortedDictionary      00:00:00.0313571
    *RBTree                 00:00:09.2414156
    **SortedSet             00:00:00.0241973
    

    RBTree与上述相同(从SortedSet类中剥离出来)。 我也尝试了400000个值,但是RBTree似乎花了很长时间 ,我真的不知道为什么。

    4 回复  |  直到 15 年前
        1
  •  17
  •   LukeH    15 年前

    你脑子里有个虫子 Node<T> 班级。当调用只接受一个值参数的构造函数时,应该设置 IsRed true

    我想这个问题已经解决了 节点<T> 类应该如下所示:

    public sealed class Node<T>
    {
        public T Item { get; private set; }
        public bool IsRed { get; set; }
        public Node<T> Left { get; set; }
        public Node<T> Right { get; set; }
    
        public Node(T value)
        {
            Item = value;
            IsRed = true;
        }
    
        public Node(T value, bool isRed)
        {
            Item = value;
            IsRed = isRed;
        }
    }
    

    另一个选择——我的偏好——是完全省略构造函数,并且总是需要 伊斯雷德 要在实例化新节点时显式设置:

    public sealed class Node<T>
    {
        public T Item { get; private set; }
        public bool IsRed { get; set; }
        public Node<T> Left { get; set; }
        public Node<T> Right { get; set; }
    
        public Node(T value, bool isRed)
        {
            Item = value;
            IsRed = isRed;
        }
    }
    

    Add 方法。。。

    Node<T> current = new Node<T>(item);
    

    Node<T> current = new Node<T>(item, true);
    
        2
  •  3
  •   Daniel MoÅ¡mondor    15 年前
    1. 颠倒测试顺序,重复测量。
    2. 随机化你的数据。插入预排序数据时,排序集的行为异常。
        3
  •  1
  •   Ian Mercer    15 年前

    SortedSet包括 TargetedPatchingOptOut 属性,你复制的版本包括这个吗?

    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    public bool Add(T item)
    {
        return this.AddIfNotPresent(item);
    }
    
        4
  •  0
  •   Filip Navara    15 年前

    如果差异不是那么大,我认为原因是.NET程序集是非结构化的,因此它们已经被转换为本机代码。对于您的类,将IL代码编译为本机代码的时间将在测试期间进行分摊。增加循环迭代次数对时间有何影响?

    推荐文章