C# 4.0 并行递归慢于;顺序的;递归。我做错了吗?

C# 4.0 并行递归慢于;顺序的;递归。我做错了吗?,c#-4.0,recursion,parallel-processing,C# 4.0,Recursion,Parallel Processing,我正在做一些非常基本的测试,看看在我的程序中使用并行处理是否能提供任何明显的速度提升。到目前为止,我对结果感到困惑。在我的测试中,我正在构建一个分支因子为30的树结构。首先,我使用非并行性进行测试,然后使用并行for循环尝试同样的事情。以下是我的结果: 顺序: Depth: 2 Time: 0.0013964 (900 nodes) Depth: 3 Time: 0.0053703 (27,000 nodes) Depth: 4 Time: 0.3994147 (810,000 nodes) D

我正在做一些非常基本的测试,看看在我的程序中使用并行处理是否能提供任何明显的速度提升。到目前为止,我对结果感到困惑。在我的测试中,我正在构建一个分支因子为30的树结构。首先,我使用非并行性进行测试,然后使用并行for循环尝试同样的事情。以下是我的结果:

顺序:

Depth: 2 Time: 0.0013964 (900 nodes)
Depth: 3 Time: 0.0053703 (27,000 nodes)
Depth: 4 Time: 0.3994147 (810,000 nodes)
Depth: 5 Time: 14.8306510 (24,300,000 nodes)
Depth: 6 Time: 6:54.4050838 (729,000,000 nodes)
平行:

Depth: 2 Time: 0.0389201 (900 nodes)
Depth: 3 Time: 0.1180270 (27,000 nodes)
Depth: 4 Time: 6:06.2296531 (810,000 nodes)
我没有费心做进一步的测试,因为我看不出在6深度下需要不到7分钟的时间

我有一个双核处理器,虽然我知道并行性有一定的开销,但我认为它不会那么重要。我已经验证了在这两种情况下生成的树结构都正确地形成到指定的深度,每个节点上都有适当数量的子节点(30)

这是我的密码:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ParallelRecursion
{
    class TreeStructure
    {
        public TreeStructure(int targetLevel, bool runParallel)
        {
            _root = new TreeNode(targetLevel, runParallel);
        }

        private TreeNode _root; 
    }

    class TreeNode
    {
        public TreeNode(int targetLevel, bool runParallel)
        {
            _runParallel = runParallel;

            _rnd = new Random();
            _score = _rnd.Next(int.MinValue, int.MaxValue);

            _level = 0;
            _targetlevel = targetLevel;

            if (_level < _targetlevel)
            {
                if (!_runParallel)
                {
                    _children = new List<TreeNode>();
                    GenerateChildren();
                }
                else
                {
                    _concurrentChildren = new ConcurrentBag<TreeNode>();
                    GenerateParallelChildren();
                }
            }
        }

        public TreeNode(TreeNode treeNode)
        {
            _runParallel = treeNode._runParallel;

            _rnd = treeNode._rnd;
            _score = _rnd.Next(int.MinValue, int.MaxValue);
            _parent = treeNode;

            _level = treeNode._level + 1;
            _targetlevel = treeNode._targetlevel;

            if (_level < _targetlevel)
            {
                if (!_runParallel)
                {
                    _children = new List<TreeNode>();
                    GenerateChildren();
                }
                else
                {
                    _concurrentChildren = new ConcurrentBag<TreeNode>();
                    GenerateParallelChildren();
                }                
            }
        }

        private bool _runParallel;
        private Random _rnd;
        private int _score;
        private int _level;
        private int _targetlevel;
        private TreeNode _parent;
        private List<TreeNode> _children;
        private ConcurrentBag<TreeNode> _concurrentChildren;

        private void GenerateChildren()
        {
            for (int i = 0; i < 30; i++)
            {
                _children.Add(new TreeNode(this));
            }
        }        

        private void GenerateParallelChildren()
        {
            Parallel.For(0, 30, i => { GenerateChild(); });
        }

        private void GenerateChild()
        {
            _concurrentChildren.Add(new TreeNode(this));
        }
    }
}

我希望我做错了什么。只是这种结构不利于并行性吗?

您使用的并行性是错误的,您正在启动一个新任务来创建一个节点,这就是为什么并行版本比较慢的原因,因为尽管TPL实际上没有为每个迭代创建一个任务,但它仍然包含一些任务,而且创建任务在时间上很昂贵(没有线程那么多)


你应该做的是分而治之,分而治之,让一个任务创建一堆树节点,而不仅仅是一个。

你错误地使用了并行,你启动了一个新任务来创建一个节点,这就是为什么并行版本比较慢,因为尽管TPL实际上并没有为每个迭代创建一个任务,但它仍然会打包其中的一些任务,而且创建任务的时间成本很高(没有线程那么高)


你应该做的是分而治之,分而治之,让一项任务创建一堆树节点,而不是一个树节点。

在一种情况下使用
ConcurrentBag
,在另一种情况下使用
List
,并不能进行苹果之间的比较。一旦将非并发子系统的
List
替换为
ConcurrentBag
,运行这两个版本的速度就会大致相同。

在一种情况下使用
ConcurrentBag
,而在另一种情况下使用
List
,并不意味着可以对苹果进行比较。一旦您将非并发子项的
List
替换为
ConcurrentBag
,运行两个版本的速度就会大致相同。

我尝试过使用ConcurrentBag对这两个版本进行测试,然后两个版本都没有子项列表(只有子项对父项的引用保持树)。在这两种情况下,速度差被大大均衡。尽管如此,平行于深度6运行的时间是连续运行的两倍。还有什么我可能做错了吗?@Chronicide运行
for
循环(非常有效)和让方法为您运行
for
循环(效率较低)之间有一些区别。您可以用
var x=Enumerable.Range(0,30)替换非并行
for
循环。选择(n=>new TreeNode(this)).ToArray()用于进一步均衡。我认为剩下的区别来自于开始和结束大量的任务,这些任务都在争夺内存分配器。啊,我明白你的意思了。那么,你认为是我对并行化的理解不足阻碍了我获得任何明显的速度提升,还是你认为问题本身不适合并行化?(或者两者都有…,但我不知道你是否认为有任何方法可以并行化,从而提高速度…@Chronicide我认为问题本身很难很好地并行化,因为所有线程最终都在竞争同一个分配器。尝试预先分配所有节点,看看将它们连接到一棵树中并行运行是否比顺序运行更快。我尝试使用ConcurrentBag对这两个节点进行测试,然后两个节点都没有子节点列表(只有子节点对父节点的引用保持树)。在这两种情况下,速度差被大大均衡。尽管如此,平行于深度6运行的时间是连续运行的两倍。还有什么我可能做错了吗?@Chronicide运行
for
循环(非常有效)和让方法为您运行
for
循环(效率较低)之间有一些区别。您可以用
var x=Enumerable.Range(0,30)替换非并行
for
循环。选择(n=>new TreeNode(this)).ToArray()用于进一步均衡。我认为剩下的区别来自于开始和结束大量的任务,这些任务都在争夺内存分配器。啊,我明白你的意思了。那么,你认为是我对并行化的理解不足阻碍了我获得任何明显的速度提升,还是你认为问题本身不适合并行化?(或者两者都有…,但我不知道你是否认为有任何方法可以并行化,从而提高速度…@Chronicide我认为问题本身很难很好地并行化,因为所有线程最终都在竞争同一个分配器。尝试预先分配所有节点,看看将它们连接到一个树中并行运行是否比按顺序运行快。我已经测试过,在一个额外的线程中生成所有三十个子线程(而不是每个子线程生成一个线程)。这实际上使并行版本的速度提高了近一倍。也许我误读了你的答案。时间上的差异是惊人的,因为你使用的是ConcurrentBag,而这个类有锁定机制来减慢代码的速度。这样,再加上任务创建开销,会使事情变得更慢。不过我还是希望看到你正在计算时间结果的代码,你能发布它吗?在移除ConcurrentBag后,我又做了一次测试。现在,e
TreeStructure ts = new TreeStructure(4, true);//TreeStructure(int targetDepth, bool runParallel)