15

私は、Grasshopper (Rhino3D の一部) と呼ばれるパッケージ内で C#.net を使用して、いくつかの数学演算を最適化する実験を行っています。操作は非常に単純ですが、実行する必要があるリストは大きく、さらに大きくなる可能性があります。

C# スクリプトで Parallel.ForEach とリストを使用していますが、得られる最終結果の数が予想よりも少なくなっています。これはおそらく、list.add がスレッド セーフではない (または、私が構築しているソフトウェア内でスレッド セーフではない) ためです。

  private void RunScript(double z, int x, List<double> y, ref object A)
  {
    List<double> temp = new List<double>();
    double r;
    System.Threading.Tasks.Parallel.ForEach(y, numb =>
      {
      r = Math.Pow((numb * x), z);
      temp.Add(r);
      });
    A = temp;

CPU マルチスレッドを使用して、数百の値に対してこの単純な数学演算を実行する簡単で効率的な方法を見つけてください (または、GPU CUDA に関する提案がある場合)。

私が知る限り、通常の C#.Net/Python/VB.Net と同じように動作するため、あいまいで特定のソフトウェアに悩まされないことを願っています。

4

5 に答える 5

15

ご想像のとおり、List<T>スレッドセーフではありません。そのインスタンスへのアクセスを同期する必要があります。

1 つのオプションは、各タスクで単純に同期することです。

private void RunScript(double z, int x, List<double> y, ref object A)
{
    List<double> temp = new List<double>();
    object l = new object();
    System.Threading.Tasks.Parallel.ForEach(y, numb =>
    {
      double r = Math.Pow((numb * x), z);
      lock (l) temp.Add(r);
    });
    A = temp;
}

注:コードには別のバグもありました。すべてのタスクで同じ変数を共有していたrため、同じ値が結果に 2 回以上追加され、他の値が除外される可能性がありました。ForEach()呼び出しに使用される匿名メソッドの本体に変数​​宣言を移動するだけで、バグを修正しました。


もう 1 つの選択肢は、得られる結果の数が事前にわかっていることを認識することです。したがって、すべての結果を含むのに十分な大きさの配列を単純に初期化できます。

private void RunScript(double z, int x, List<double> y, ref object A)
{
    double[] results = new double[y.Count];
    System.Threading.Tasks.Parallel.For(0, y.Count, i =>
    {
      // read-only access of `y` is thread-safe:
      results[i] = Math.Pow((y[i] * x), z);
    });
    A = new List<double>(results);
}

2 つのスレッドが配列内の同じ要素にアクセスしようとするresultsことはなく、配列自体が変更される (つまり、再割り当てされる) ことはないため、これは完全にスレッドセーフです。

List<double>上記は、出力オブジェクトとしてa が本当に必要であると仮定しています。もちろん、配列が満足のいくものであれば、それをコンストラクターに渡す代わりに単に割り当てresultsて、最後にまったく新しいオブジェクトを作成することができます。AList<T>

于 2015-04-17T22:12:15.750 に答える
7

より簡単な解決策は、代わり.AsParallel()に結果を使用して作業することです。ParallelEnumerable

private void RunScript(double z, int x, List<double> y, ref object A)
{
    A = y
        .AsParallel().AsOrdered()
        .Select(elem => Math.Pow((elem * x), z))
        .ToList();
}
于 2015-04-17T22:30:28.193 に答える
2

別のオプションを次に示します。

    private void RunScript(double z, int x, List<double> y, ref object A) {
        var temp = new System.Collections.Concurrent.BlockingCollection<double>();
        System.Threading.Tasks.Parallel.ForEach(y, numb => {
            double r = Math.Pow((numb * x), z);
            temp.Add(r);
        });
        A = temp; // if needed you can A = temp.ToList();
        }

ピーターはあなたのコードの問題点をうまくまとめてくれました。彼が提案する 2 番目の関数がおそらく最良の選択肢だと思います。それでも、代替案を見て、.NET フレームワークに並行セーフ コレクションが含まれていることを学ぶのは良いことです。

于 2015-04-17T22:20:39.517 に答える
0

また、入力を少し変更することも検討していました。データを別々のブランチに分割し、各ブランチを別々のスレッドで計算し、最後にそれらを再結合します。ただし、スコアは 531ms で悪化しています。スクリプトが悪いことは理解していますが、それは私の考えをよく表していると思います。適切に書かれていれば、成功する可能性があります。いいえ?

  private void RunScript(double z, int x, List<double> y, DataTree<double> u, ref object A)
  {
    System.Threading.Tasks.Task<double[]> th1 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(0).ToArray(), x, z));
    System.Threading.Tasks.Task<double[]> th2 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(1).ToArray(), x, z));
    System.Threading.Tasks.Task<double[]> th3 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(2).ToArray(), x, z));
    System.Threading.Tasks.Task<double[]> th4 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(3).ToArray(), x, z));
    System.Threading.Tasks.Task<double[]> th5 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(4).ToArray(), x, z));
    System.Threading.Tasks.Task<double[]> th6 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(5).ToArray(), x, z));
    System.Threading.Tasks.Task<double[]> th7 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(6).ToArray(), x, z));
    System.Threading.Tasks.Task<double[]> th8 = System.Threading.Tasks.Task<double[]>.Factory.StartNew(() => mP(u.Branch(7).ToArray(), x, z));

    List<double> list = new List<double>();

    list.AddRange(th1.Result);
    list.AddRange(th2.Result);
    list.AddRange(th3.Result);
    list.AddRange(th4.Result);
    list.AddRange(th5.Result);
    list.AddRange(th6.Result);
    list.AddRange(th7.Result);
    list.AddRange(th8.Result);


    A = list;


  }

申し訳ありませんが、「使用中」に追加することはできません

于 2015-04-18T22:45:43.283 に答える
0

ご意見をお寄せいただきありがとうございます。プロファイラーの出力に興味がある場合は、次のようになります。

ピーター・ドゥニーホ 第一オプション:330ms

Peter Duniho 2番目のオプション: 207ms

ドウィーバリーオプション: 335ms

Mattias Buelens オプション: 376ms

これはおそらく非常に奇妙です.netスクリプトはグラスホッパーでより速く実行する必要があります(.netであるため)が、129msのPython並列計算に勝るソリューションはありません!

とにかく、詳細な回答をありがとうございました!あなたは素晴らしいです!

于 2015-04-17T23:14:26.780 に答える