web-dev-qa-db-ja.com

AsParallel()/ Parallel.ForEach()ガイドラインを使用していますか?

AsParallel()またはParallel.ForEach()を活用してこれを高速化するためのアドバイスを探しています。

私が持っている方法(この例では簡略化/ろくでなし)を参照してください。

「APAC」は、他の50の「US、FR、JP、IT、GB」などの国のエイリアスである「US、FR、APAC」のようなリストを取ります。メソッドは「US、FR、APAC」を取り、「US」、「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()を使用することにニュアンスがありますか? foreachの代わりにParallel.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();
}
_
42

いくつかのポイント。

ただcountriesAndAliases.AsParallel()と書くだけでは役に立ちません。 AsParallel()は、並行して実行された後に来るLinqクエリの一部を作成します。パーツは空なので、まったく使用できません。

通常、foreachParallel.ForEach()に置き換える必要があります。ただし、スレッドセーフでないコードには注意してください!あなたはそれを持っている。 foreachにラップすることはできません。なぜならList<T>.Add自体はスレッドセーフではありません。

あなたはこのようにする必要があります(申し訳ありませんが、テストしませんでしたが、コンパイルされます):

        return countriesAndAliases
            .AsParallel()
            .SelectMany(s => 
                IsCountryNotAlias(s)
                    ? Enumerable.Repeat(s,1)
                    : AliasCountryLists[s]
                ).Distinct();

編集

さらに2つのことを確認する必要があります。

  1. IsCountryNotAliasはスレッドセーフでなければなりません。 純粋な関数 の場合はさらに良いでしょう。
  2. 辞書はスレッドセーフではないため、その間は誰もAliasCountryListsを変更しません。または ConcurrentDictionary を使用して確認してください。

役立つリンク:

並列プログラミングのパターン:.NET Framework 4での並列パターンの理解と適用

。NET 4コーディングガイドラインの並列プログラミング

Parallel.ForEachを使用すべき場合?PLINQを使用すべき場合

[〜#〜] ps [〜#〜]:ご覧のとおり、新しい並列機能は見た目(および感触)ほど明白ではありません。

68
Andrey

AsParallel()を使用するときは、体がスレッドセーフであることを確認する必要があります。残念ながら、上記のコードは機能しません。 _List<T>_はスレッドセーフではないため、AsParallel()を追加すると競合状態が発生します。

ただし、コレクションを System.Collections.Concurrent のコレクションを使用するように切り替える場合、 _ConcurrentBag<T>_ 、上記のコードはおそらく動作します。

13
Reed Copsey

各エイリアスに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();
}

注:コードは擬似コードとして表示する必要があります。

3
cjg

これは、本質的にシリアル操作のように思えます。あなたがしているのは、文字列のリストをループして別のリストに挿入することだけです。並列化ライブラリーはそれを実行し、さらに多数のスレッド化と同期化を行います-おそらく遅くなるでしょう。

また、重複したくない場合は、HashSet<string>を使用する必要があります。

0
Steve M