Мне нужно быстро пересечь дерево, и я хотел бы сделать это параллельно. Я предпочел бы использовать параллельные расширения, чем вручную, чтобы развернуть кучу потоков.
Мой текущий код выглядит примерно так:
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. Если прочитать его правильно, это все равно не сработает, потому что я мутирую очередь, которую я пытаюсь повторить.
Нужно ли использовать задачу factory и рекурсию (и это опасно?)? или есть какое-то простое решение, которое я пропускаю?
Изменить: @svick
Дерево имеет чуть более 250 000 узлов. Максимальная глубина сейчас составляет 14 узлов, включая корень.
От корня осталось около 500 узлов, а после этого баланс имеет довольно случайное распределение. Я скоро получу лучшую статистику по распространению.
@Enigmativity:
Да, дерево изменяется одновременно со многими пользователями, но обычно я использую общую блокировку чтения для дерева или поддерева или допускаю грязные чтения.
Вызов node. Дети могут считаться атомарными.
DoSomething действительно является одним из нескольких делегатов, для некоторых дорогостоящих операций я, вероятно, собираю список узлов и обрабатываю их за пределами обхода.
Я понял, что я должен, вероятно, посмотреть на общий случай (вместо того, чтобы все дерево было перемещено под деревом). С этой целью я провел траверс на каждом node дерева и посмотрел на общее время.
Я использовал Parallel.ForEach(узлы, Traverse) для каждого алгоритма обхода, где узлы содержали все узлы ~ 250k. Это симулированное (вроде) множество пользователей одновременно запрашивает множество разных узлов.
00256ms Ширина первого последовательного
00323ms Ширина Первый Последовательный с работой (я увеличил статический счетчик как "работа" )
01495ms Кирки Первый ответ
01143ms Svicks Второй ответ
00000ms Рекурсивный одиночный Резьбованный не закончил после 60 секунд
00000ms. Ответ за энигматизм не закончился после 60 секунд
@Enigma, я думаю, что возможно, я каким-то образом перепутал ваш алогрит, потому что кажется, что это должно быть намного быстрее.
Результаты удивили меня, если не сказать больше. Мне пришлось добавить некоторую работу в первую очередь, чтобы убедить себя, что компилятор не магически оптимизирует проходы.
Для единственного обхода головы распараллеливание первого уровня имело только лучшую производительность. Но едва ли это число улучшилось, так как я добавил больше узлов ко второму уровню (2000 вместо 500).