我想加快遍历树的过程。以下是节点的示例:
class Node
{
public List<Node> Children { get; set; }
public int SompeProperty { get; set; }
public String SomeOtherProperty { get; set; }
}
我遍历尝试的方式如下:
static void TraverseTree(Node ParentNode)
{
if (ParentNode.Children == null)
return;
foreach (var child in ParentNode.Children)
{
TraverseTree(child);
}
}
ParentNode.Children
方法大约需要1毫秒,因为Node表示文件或目录。我只是用这个节点的例子来说明我的观点。
所以如果你考虑一下,如果第一个节点有4个子节点,并且每个子节点都有10000000个后代,那么如果我们在separeate线程中利用并行遍历这4个子节点中的每个子节点,我们可以提高这种遍历的速度节目。如果那就是情景那么我会采取这种方法。但如果我事先不知道树的结构怎么能这样做呢?
我一直在考虑:
1)开始遍历树,将前10个节点放在堆栈上,然后在单独的线程上开始遍历每个节点。
2)做类似的事情:
static void TraverseTree(Node ParentNode)
{
if (ParentNode.Children == null)
return;
foreach (var child in ParentNode.Children)
{
ThreadPool.QueueUserWorkItem(new WaitCallback((x) =>
{
TraverseTree(child);
}), null);
}
}
这经常给我带来奇怪的结果,但速度要快得多。
使用任务提高了算法的速度约40%,结果如下:
使用以下算法扫描我的整个C:\驱动器大约需要 5.81 秒:
//directoryPath = "C:\"
var now = DateTime.Now;
Task<List<ScanItem>> t1 = new Task<List<ScanItem>>(() =>
{
return GetAllFilesInDirectory(directoryPath);
});
t1.Start();
t1.Wait();
var done = DateTime.Now-now; // done = 5.81 average
使用以下算法扫描整个C:\驱动器大约需要 3.01 秒:
//directoryPath = "C:\"
var now = DateTime.Now;
// get all directories in my c: drive it should only contain directories
var directories = Directory.GetDirectories(directoryPath);
// directories = 17 directories: inetpub, MSOCache, PrefLogs, ProgramFiles, ProgramFiles (x86) etc...
Task<List<ScanItem>>[] myTasks = new Task<List<ScanItem>>[directories.Length];
// create a task fore each directory in the c:\ drive
for (int k = 0; k < myTasks.Length; k++)
{
var currentDir = directories[k];
myTasks[k] = new Task<List<ScanItem>>(() =>
{
return GetAllFilesInDirectory(currentDir);
});
}
// start all the tasks
for (int k = 0; k < myTasks.Length; k++)
myTasks[k].Start();
Task.WaitAll(myTasks); // wait for all tasks to finish
var done = now - DateTime.Now; // average about 3.01 seconds
如果我遍历列表,第一个算法返回318,222个文件和目录(即正确的数字)。第二个算法返回318,195这是非常接近我不明白为什么虽然......
我在具有8个核心的计算机上测试它。也许如果我在使用一个任务拥有2个内核的计算机上运行它可能比创建所有这17个任务更快。
如果你想知道我用什么算法来快速获取文件https://stackoverflow.com/a/724184/637142
答案 0 :(得分:12)
使用任务并行库,而不是滚动自己的并行代码。它非常适合解决这类问题。
TPL的工作方式不是为问题分配线程,而是将问题分解为“任务”,让TPL负责确定如何在可用工作池之间并行化工作。只需为树的每个子分支创建一个任务;这些任务可以反过来为他们的子分支产生自己的任务。 TPL将从池中分配线程,直到处理器饱和。
因此,让TPL了解您的任务是否将在CPU或I / O上进行门控非常重要:
如果任务是CPU绑定的,那么TPL将为每个CPU分配一个池化线程,并使其他任务等待,直到有可用的核心;最大化吞吐量并使所有处理器饱和。这正是你想要的:如果你买了一台带有四个处理器并且其中两个闲置的机器,那么你支付了两个你没有使用的核心。
如果单个任务是I / O绑定,那么您可以在创建任务时使用LongRunning
选项向TPL指示此任务不应占用整个核心;其他任务应该转向那个核心。
如果看起来像是许多 I / O绑定任务,那么您应该考虑使用 TaskCompletionSource ,因为它允许更有效地使用“延续”回调。还要考虑使用C#5的新async/await
功能来安排延续;它提供了一种更愉快的编写异步代码的方式。
当然,不要忘记,如果问题实际上是使机器的I / O能力饱和,那么处理器并行性的数量就不会有任何影响。如果您正在填充游泳池,在同一个水龙头中添加更多软管不会增加通过该水龙头的流量。
答案 1 :(得分:2)
如果要并行遍历树,则必须:
如果你得到“奇怪的结果”,上面的一个可能不是真的。请记住,遍历节点的 order 在多线程示例中是不确定的。在声明结果“奇怪”时,您是否说明了这一点?
即便如此:
答案 2 :(得分:1)
请记住,多线程仅在您的应用程序在单个核心上占用100%的CPU时间时才有用;如果CPU使用率很低(因为它在硬盘驱动器或网络之后等待),您将看不到并行运行代码的任何好处。
答案 3 :(得分:-1)
最近我必须创建一个能够发现巨大树结构的算法(实际上是文件系统,但它可以是任何东西)并在每个项目上执行异步操作。我想出了一个小型库(使用.Net TPL和并发队列构建),能够做到这一点: