【问题标题】:LINQ List.AsParallel with boolean returnLINQ List.AsParallel 与布尔返回
【发布时间】:2013-05-30 06:17:58
【问题描述】:

我有一个 static List<long> primes 的所有已知素数直到某个点,以及这样的函数:

static bool isPrime(long p)
{
    double rootP = Math.Sqrt(p);

    foreach (long q in primes)
    {
        if (p % q == 0)
            return false;

        if (q >= rootP)
            return true;
    }

    return true;
}

可以这样并行化:

static bool isPrime(long p)
{
    double rootP = Math.Sqrt(p);

    primes.AsParallel().ForAll(q =>
        {
            if (p % q == 0)
                return false;

            if (q > rootP)
                break;
        });

    return true;
}

但是,这会产生一个编译时错误,指出我的块中的某些返回类型不能隐式转换为委托返回类型。

我对 LINQ 比较陌生,尤其是 PLINQ。对我来说,这似乎是并行性的一个很好的候选,因为每个已知素数与候选素数的检查是一个独立的过程。

是否有一种简单的方法可以修复我的块以使其正常工作,还是我需要以完全不同的方式解决这个问题?

【问题讨论】:

  • 它有一个非常短的(时间上的)内部循环,所以 AsParallel() 不太可能有太大帮助 - 它可能会比非并行循环慢。您最好使用PartitionerParallel.ForEach(),如图所示in my recent answer here 这并不能直接帮助您解决return false 问题;您必须在循环中设置一个共享布尔值并在循环中检查它以在必要时提前退出循环,然后在外部循环完成后返回布尔值。

标签: c# linq lambda plinq


【解决方案1】:

在语法方面,您在代码中犯了两个错误:

  1. lambda 中的 return 不会从封闭方法返回,而只是从 lambda 中返回,因此您的 return false; 将无法正常工作。
  2. 你不能 break 离开 lambda。即使那个lambda是循环执行的,那个循环对你来说基本上是不可见的,你当然不能直接控制它。

我认为修复代码的最佳方法是使用专门为此目的制作的 LINQ 方法:Any(),以及 TakeWhile() 以过滤掉过大的素数:

static bool IsPrime(long p)
{
    double rootP = Math.Sqrt(p);

    return !primes.AsParallel().TakeWhile(q => q > rootP).Any(q => p % q == 0);
}

但你的推理也有缺陷:

在我看来,这似乎是一个很好的并行候选者,因为每个已知素数与候选素数的检查是一个独立的过程。

事情没有那么简单。检查每个素数也是一个极其简单的过程。这意味着简单并行化的开销(就像我上面建议的那样)可能大于性能提升。更复杂的解决方案(如 Matthew Watson 在评论中建议的解决方案)可以帮助解决这个问题。

【讨论】:

    【解决方案2】:

    这是解决您问题的方法:

    static List<long> primes = new List<long>() {
      2,3,5,7,11,13,17,19,23
    };
    
    static bool isPrime(long p) {
      var sqrt = (long)Math.Sqrt(p);
      if (sqrt * sqrt == p)
        return (false);
      return (primes.AsParallel().All(n => n > sqrt || p % n != 0));
    }
    

    它甚至试图减少二次数的并行性,一旦找到第一个候选者,它就会停止检查更多候选者。

    【讨论】:

    • 我不确定这是一个多么大的胜利。如果您有一千个素数并调用isPrime(2),您可以确定此代码将不必要地检查所有素数。
    【解决方案3】:

    发生错误是因为如果您的条件

    p % q == 0
    

    true,闭包将返回false,当

    q > rootP
    

    它会中断并且什么也不返回。这将在 PHP 中有效,但在 .NET 中无效:P

    lambda 是一个完全匿名的函数,返回类型必须始终保持一致。

    您必须在这里重新设计您的代码。您在非并行示例中已正确完成...只需将 break 替换为 return true (那时它不会更漂亮,但它应该可以工作)。

    【讨论】:

      【解决方案4】:

      改用 Parallel.For 可能更容易

      static volatile bool result = true;
      static bool isPrime(long p)
      {
          double rootP = Math.Sqrt(p);
      
          Parallel.For(0, primes.Count, (q, state) =>
              {
                  if (p % q == 0)
                  {
                      result = false;
                      state.Break();
                  }
      
                  if (q > rootP)
                      state.Break();
              });
      
          return result;
      }
      

      【讨论】:

      • 我不相信局部变量可以声明为volatile;它必须是字段成员。
      • @DanLugg:很好的观察。我希望 result 变量被委托捕获并由编译器提升到成员变量,但看起来它不起作用。感谢您的评论。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-14
      • 2013-08-18
      • 2011-12-14
      • 2011-11-20
      相关资源
      最近更新 更多