Вложенные пары Parallel.ForEach в том же списке?
Мне нужно распараллелить метод, который делает исчерпывающее парное сравнение элементов в списке. Последовательная реализация проста:
foreach (var element1 in list)
foreach (var element2 in list)
foo(element1, element2);
В этом случае foo не изменит состояние элемента1 или element2. Я знаю, что небезопасно просто выполнять вложенные инструкции Parallel.ForEach:
Parallel.ForEach(list, delegate(A element1)
{
Parallel.ForEach(list, delegate(A element2)
{
foo(element1, element2);
});
});
Каким будет идеальный способ реализовать это с помощью библиотеки параллельных задач?
Ответы
Ответ 1
Разве у вас не было бы одного параллельного и одного нормального цикла? Поэтому либо
Parallel.ForEach(list, delegate(A element1)
{
foreach(A element2 in list)
foo(element1, element2)
});
или
foreach(A element1 in list)
{
Parallel.ForEach(list, delegate(A element2)
{
foo(element1, element2);
});
}
Следует также ускорить его. В любом случае никогда не будет нитки за такт, так что это, вероятно, будет так же быстро или немного медленнее, чем вложенные параллельные петли.
Ответ 2
По крайней мере, если вы выполняете код на компьютере, где количество ядер не менее чем в два раза превышает количество элементов в списке, я не уверен, что это хорошая идея для встроенных Parallel.ForEach
s.
Другими словами, если вы нацеливаете четырехъядерный процессор, а в списке содержится одна тысяча элементов, просто распараллелите родительский цикл. Параллелизация обоих циклов не сделает код более быстрым, а скорее намного, гораздо медленнее, поскольку параллельные задачи имеют производительность.
alt text http://www.freeimagehosting.net/uploads/ca97f403f8.png
На каждой итерации через несколько секунд будет потеряно несколько миллисекунд, чтобы определить, какой поток должен выполнить следующую итерацию. Скажем, у вас есть набор из 7 предметов. Если вы распараллеливаете родительский цикл, эти миллисекунды будут потеряны 7 раз. Если вы распараллеливаете обе петли, они будут потеряны 7 × 7 = 49 раз. Чем больше набор, тем больше перегрев.
Ответ 3
Два вложенных цикла, по существу, означают, что вы хотите сфотографировать картографическое произведение списка. Вы можете распараллелить всю операцию, сначала создав все пары во временном списке, а затем перейдя через этот список с помощью Parallel.ForEach.
EDIT. Вместо создания списка всех комбинаций вы можете использовать итератор для возврата двухэлементного кортежа с комбинацией. Parallel.ForEach все равно будет распараллелить обработку кортежей.
Следующий пример распечатывает текущий шаг итерации, чтобы показать, что результаты возвращаются из строя, как и ожидалось при параллельной обработке:
const int SIZE = 10;
static void Main(string[] args)
{
List<int> list = new List<int>(SIZE);
for(int i=0;i<SIZE;i++)
{
list.Add(i);
}
Parallel.ForEach(GetCombinations(list),(t,state,l)=>
Console.WriteLine("{0},{1},{2}",l,t.Item1,t.Item2));
}
static IEnumerable<Tuple<int,int>> GetCombinations(List<int> list)
{
for(int i=0;i<list.Count;i++)
for(int j=0;j<list.Count;j++)
yield return Tuple.Create(list[i],list[j]);
}