Parallel tree traversal in C#

前端 未结 5 2024
轻奢々
轻奢々 2021-02-05 19:28

I need to traverse a tree quickly, and I would like to do it in parallel. I\'d rather use the parallel extensions than manually spin up a bunch of threads.

My current co

5条回答
  •  鱼传尺愫
    2021-02-05 19:52

    Since the traversal of the tree is extremely fast, that calls to Children are atomic, and that it is the expensive nature of the DoSomething delegates that need to be executed in parallel, here's my take on the solution.

    I started with the idea that I needed a function that takes a node as a parameter, creates a task that executes DoSomething, recursively calls itself to create tasks for all of the children nodes, and finally returns a Task that waits for all of the internal tasks to be completed.

    Here it is:

    Func createTask = null;
    createTask = n =>
    {
        var nt = Task.Factory.StartNew(() =>
        {
            if (n.Property == someValue)
                DoSomething(n);
        });
        var nts = (new [] { nt, })
            .Concat(n.Children.Select(cn => createTask(cn)))
            .ToArray();
    
        return Task.Factory.ContinueWhenAll(nts, ts => { });
    };
    

    All that is required to call it and wait for the traversal to complete is:

    createTask(root).Wait();
    

    I tested this by creating a tree of nodes with 500 children off of the root with 14 levels, with 1 or 2 subsequent children per node. This gave me a total of 319,501 nodes.

    I created a DoSomething method that performed some work - for (var i = 0; i < 100000 ; i++) { }; - and then ran the above code and compared it to processing the same tree in series.

    The parallel version took 5,151 ms. The sequential version 13,746 ms.

    I also performed a test where I reduced the number of nodes to 3,196 and increased the processing time for DoSomething by 100x. The TPL very cleverly reverts to running sequentially if its tasks complete quickly so lengthening the processing time made the code run with more parallelism.

    Now the parallel version took 3,203ms. The sequential version took 11,581ms. And, if I only called the createTask(root) function without waiting for it to complete it took just 126ms. This means that the tree is traversed very quickly, and it would then make sense to lock the tree during traversal and unlock it when processing is taking place.

    I hope this helps.

提交回复
热议问题