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>不是线程安全的。您必须同步对它的任何实例的访问。

一种选择是简单地在每个任务中同步:

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变量,这可能导致相同的值被添加到结果中两次或更多次,而其他值被忽略。我通过简单地将变量声明移动到用于ForEach()调用的匿名方法的主体来修复了该错误。


另一种选择是认识到您事先知道将有多少结果,因此可以简单地初始化一个足够大的数组以包含所有结果:

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);
}

没有两个线程会尝试访问数组中的同一个元素results,并且数组本身永远不会改变(即被重新分配),所以这是完全线程安全的。

以上假设您确实需要 aList<double>作为输出对象。当然,如果一个数组是令人满意的,那么你可以只分配resultsA而不是将它传递给List<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();
        }

Peter 很好地概述了您的代码问题,我认为他建议的第二个功能可能是您的最佳选择。仍然很高兴看到替代方案并了解 .NET 框架包含并发安全集合。

于 2015-04-17T22:20:39.517 回答
0

非常感谢您的意见!如果您对分析器输出感兴趣,如下所示:

Peter Duniho 第一个选项:330ms

Peter Duniho 第二个选项:207ms

Dweeberly 选项:335 毫秒

Mattias Buelens 选项:376ms

这很奇怪,因为 .net 脚本必须在蚱蜢中运行得更快(因为它是 .net),但是您的解决方案都没有超过 129 毫秒的 python 并行计算!

无论如何,感谢你们所有的详细答案!你很棒!

于 2015-04-17T23:14:26.780 回答
0

我也在考虑稍微改变输入。将数据拆分为单独的分支,在单独的线程上计算每个分支,然后在最后重新组合它们。然而,它在 531 毫秒时得分更差。我知道剧本不好,但我认为它很好地展示了我的想法,如果写得好可能会成功。不是吗?

  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 回答