Мне нужно быстро пройти по дереву, и я хотел бы сделать это параллельно. Я бы предпочел использовать параллельные расширения, чем вручную раскручивать кучу потоков.
Мой текущий код выглядит примерно так:
public void Traverse(Node root)
{
var nodeQueue = new Queue<Node>();
nodeQueue.Enqueue(root);
while (nodeQueue.Count!=0)
{
var node = nodeQueue.Dequeue();
if (node.Property = someValue) DoSomething(node);
foreach (var node in node.Children)
{
nodeQueue.Enqueue(node);
}
}
}
Я действительно надеялся, что у Parallel.ForEach есть аналог Parallel.While. Я натолкнулся на статью Стивена Туба о Внедрение параллели при использовании Parallel.ForEach . Если прочитать его правильно, это все равно не сработает, потому что я изменяю очередь, которую пытаюсь повторить.
Нужно ли использовать фабрику задач и рекурсию (и это рискованно?)? или есть какое-то простое решение, которое я пропускаю?
Edit:
@svick
В дереве чуть более 250 000 узлов.
Максимальная глубина сейчас составляет 14 узлов, включая корень.
Есть около 500 узлов вне корня, и остаток после этого имеет довольно случайное распределение.
Скоро я получу лучшую статистику по дистрибутиву.
@ Энигмативность:
Да, дерево модифицируется одновременно многими пользователями, но у меня обычно будет общая блокировка чтения для дерева или поддерева, или разрешено грязное чтение.
Звонки на узел. Детей можно считать атомарными.
DoSomething действительно один из нескольких делегатов, для некоторых дорогостоящих операций я, вероятно, соберу список снимков и обработаю их за пределами обхода.
Я понял, что мне, вероятно, следует взглянуть на общий случай (обходится дерево вместо всего дерева.) С этой целью я бегал по каждому узлу дерева и просматривал общее время.
Я использовал Parallel.ForEach (node, Traverse) для каждого алгоритма обхода, где узлы содержали все ~ 250 тыс. Узлов. Это имитировало (вроде) много пользователей, одновременно запрашивающих много разных узлов.
00256ms Ширина первая последовательность
00323ms Первый шаг ширины с работой (я увеличил статический счетчик как «работа»)
01495мс Киркс Первый ответ
01143мс Свикс Второй ответ
00000ms Рекурсивная однопоточная обработка не завершилась через 60 с
00000ms Ответ Энигмативности не закончился через 60 с
@ Enigma, я думаю, возможно, я как-то испортил твой алогритм, потому что, похоже, он должен быть намного быстрее.
Результаты удивили меня, если не сказать больше.
Я должен был добавить некоторую работу к первой последовательности, чтобы убедить себя, что компилятор магически не оптимизирует обходы.
Для одиночного обхода головы распараллеливание первого уровня имело только лучшую производительность. Но только это число улучшилось, когда я добавил больше узлов на второй уровень (2000 вместо 500).