Подтвердить что ты не робот

Используете рекомендации 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();
}
4b9b3361

Ответ 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>, если вы не хотите дублировать.