Используете рекомендации AsParallel()/Parallel.ForEach()?
Ищите небольшой совет по использованию AsParallel()
или Parallel.ForEach()
, чтобы ускорить это.
См. метод, который у меня есть (упрощенный/бастардованный для этого примера) ниже.
Требуется список, например "US, FR, APAC", где "APAC" является псевдонимом для 50 других "US, FR, JP, IT, GB" и т.д. Этот метод должен принимать "US, FR, APAC" и преобразовывать его в список "США", "FR" и все страны, находящиеся в "APAC".
private IEnumerable<string> Countries (string[] countriesAndAliases)
{
var countries = new List<string>();
foreach (var countryOrAlias in countriesAndAliases)
{
if (IsCountryNotAlias(countryOrAlias))
{
countries.Add(countryOrAlias);
}
else
{
foreach (var aliasCountry in AliasCountryLists[countryOrAlias])
{
countries.Add(aliasCountry);
}
}
}
return countries.Distinct();
}
Делает ли это распараллеливание столь же простым, как изменить его на то, что ниже? Есть ли больше нюансов в использовании AsParallel()
, чем это? Должен ли я использовать Parallel.ForEach()
вместо foreach
? Какие эмпирические правила следует использовать при распараллеливании циклов foreach
?
private IEnumerable<string> Countries (string[] countriesAndAliases)
{
var countries = new List<string>();
foreach (var countryOrAlias in countriesAndAliases.AsParallel())
{
if (IsCountryNotAlias(countryOrAlias))
{
countries.Add(countryOrAlias);
}
else
{
foreach (var aliasCountry in AliasCountryLists[countryOrAlias].AsParallel())
{
countries.Add(aliasCountry);
}
}
}
return countries.Distinct();
}
Ответы
Ответ 1
Несколько точек.
написание только countriesAndAliases.AsParallel()
бесполезно. AsParallel()
выполняет часть запроса Linq, который приходит после его выполнения параллельно. Часть пуста, поэтому вообще не использовать.
обычно вам следует отменить foreach
с помощью Parallel.ForEach()
. Но остерегайтесь небезопасного кода! У тебя есть это. Вы не можете просто обернуть его в foreach
, потому что List<T>.Add
не является потокобезопасным.
поэтому вы должны сделать это (извините, я не тестировал, но компилирует):
return countriesAndAliases
.AsParallel()
.SelectMany(s =>
IsCountryNotAlias(s)
? Enumerable.Repeat(s,1)
: AliasCountryLists[s]
).Distinct();
Edit
Вы должны быть уверены в еще двух вещах:
-
IsCountryNotAlias
должен быть потокобезопасным. Было бы даже лучше, если это чистая функция.
- Никто не будет изменять
AliasCountryLists
тем временем, потому что словари не являются потокобезопасными. Или используйте ConcurrentDictionary, чтобы убедиться.
Полезные ссылки, которые помогут вам:
Шаблоны для параллельного программирования: понимание и применение параллельных шаблонов в .NET Framework 4
Параллельное программирование в .NET 4 Правила кодирования
Когда следует использовать Parallel.ForEach? Когда следует использовать PLINQ?
PS: как вы видите, новые параллельные функции не так очевидны, как они выглядят (и чувствуют).
Ответ 2
При использовании AsParallel() вы должны убедиться, что ваше тело является потокобезопасным. К сожалению, приведенный выше код не будет работать. List<T>
не является потокобезопасным, поэтому добавление AsParallel()
приведет к состоянию гонки.
Если, однако, вы переключите свои коллекции на использование коллекции в System.Collections.Concurrent, например ConcurrentBag<T>
, скорее всего, будет работать над этим кодом.
Ответ 3
Я бы предпочел использовать другую структуру данных, такую как Set для каждого алиаса, а затем использовать Set union для их объединения.
Что-то вроде этого
public string[] ExpandAliases(string[] countries){
// Alias definitions
var apac = new HashSet<string> { "US", "FR", ...};
...
var aliases = new HashMap<string, Set<string>> { {"APAC": apac}, ... };
var expanded = new HashSet<string>
foreach(var country in countries){
if(aliases.Contains(country)
expanded.Union(aliases[country]);
else{
expanded.Add(country);
}
return expanded.ToArray();
}
Примечание: код следует рассматривать как псевдокод.
Ответ 4
Мне кажется, что это по сути является серийной операцией. Все, что вы делаете, - это перебирать список строк и вставлять их в другой список. Библиотеки распараллеливания собираются сделать это, плюс множество потоков и синхронизации - вероятно, это будет медленнее.
Кроме того, вы должны использовать HashSet<string>
, если вы не хотите дублировать.