3

有谁知道检查二维数组中的所有元素都不是 NaN 或 Infinity 的最佳方法是什么?我的数组可能很大,所以我不想循环遍历它。我正在考虑使用

double[,] myArray;
if ( !double.IsNaN(myArray.Cast<double>().Min() ) && !double.IsInfinity( myArray.Cast<double>().Min() )
{
    // have finite values as elements
}

用于 NaN 和无穷大检查。我只需要知道数组中是否有任何元素是 NaN 或无穷大,所以我认为只需检查数组中的最小元素就可以解决 NaN 和无穷大的问题。有一个更好的方法吗?感谢您提供的任何帮助或建议。

4

4 回答 4

5

我个人会使用:

if (!myArray.Cast<double>().Any(d => double.IsNaN(d) || double.IsInfinity(d)))
{
     // All have correct values...

除了更短之外,这应该接近手动编写循环的速度,因为它保持单次遍历值,如果达到“坏”值则立即退出(因为Any()将停止评估失败)。


由于我的数组可能会变大,我想我会编写循环以获得更好的性能/

如果您正在处理非常大的数组,一种选择是并行执行此检查:

if (!myArray.Cast<double>().AsParallel()
            .Any(d => double.IsNaN(d) || double.IsInfinity(d)))
{
     // All have correct values...

对于非常大的阵列,这通常会执行直接循环,因为多个内核可以通过 PLINQ 处理分区数据。

于 2013-11-05T23:21:17.593 回答
3

手动编码的循环比建议的 Linq 替代方案快得多。请参阅下面的更新 2。

一种或另一种方式,您将遍历数组的每个元素来回答您的问题,因为没有为数组指定排序。

您的代码就是这样做的,隐藏了循环的实现。使用 Linq 执行循环至少比编写自己的for循环慢一点,因为它做的工作更多。(实际上,由于您没有缓存 的结果,因此您此时会遍历数组两次myArray.Cast<double>().Min())。

对于大多数处理器,您应该按行主要顺序遍历循环,因为它遍历相邻的内存地址(在我自己的测试中,以错误的顺序遍历可能会慢 30%)。

如果实际上数组中的值有某种顺序,则可能会进行更有效的搜索。

更新

我怀疑手动编码的循环会比使用 Linq 快得多,因为循环体是微不足道的。如果最后一点性能都很重要,我建议您同时实现选项和基准测试。

Reed 的并行运行建议也值得进行基准测试。您可以使用Parallel.For非常轻松地并行化传统迭代。

更新 2

测量了 Reed 解决方案和手动编码循环的性能。

Linq:1448.6353 毫秒

For 循环:125.2208 毫秒

使用的代码:

class Program
{
    const int SIZE = 3000;
    static double[,] data = new double[SIZE,SIZE];


    static void Main(string[] args)
    {
        if (args.Length >= 1 && args[0] == "/for")
        {
            Benchmark(ForLoop);
        }
        else
        {
            Benchmark(LinqLoop);
        }
    }

    static void ForLoop()
    {
        for (int i = 0; i < SIZE; i++)
        {
            for (int j = 0; j < SIZE; j++)
            {
                if (double.IsNaN(data[i, j]) || double.IsInfinity(data[i, j])) Console.WriteLine("FOUND!");
            }
        }
    }

    static void LinqLoop()
    {
        if (!data.Cast<double>().Any(d => double.IsNaN(d) || double.IsInfinity(d))) Console.WriteLine("FOUND!");
    }

    static void Benchmark(Action a)
    {
        Stopwatch watch = Stopwatch.StartNew();
        a();

        TimeSpan span = watch.Elapsed;
        Console.WriteLine("Milliseconds: " + span.TotalMilliseconds + " ms");
        Console.ReadKey();
    }
}
于 2013-11-05T23:16:46.780 回答
1

“我的数组可能很大,所以我不想循环遍历它。”

循环通过它是访问所有元素的唯一方法。以一种或另一种方式需要一个循环。

如果您担心性能,则不应使用 LINQ,因为它会产生迭代器和委托调用开销。使用unsafe代码通过将其视为一维数组来遍历该数组的所有元素。

JIT 无法优化多维数组的边界检查。

像这样:

fixed (double* arrayPtr = array) {
 var count = width * height;
 for (int i = 0; i < count; i++) {
  if (double.IsNaN(arrayPtr[i]) || double.IsInfinity(arrayPtr[i]))
   return true;
 }
}
return false;

可能比 LINQ 解决方案快 10 倍。LINQ 非常依赖间接,这对现代 CPU 的性能有害。基于直线循环的代码通常要快得多。

于 2013-11-05T23:37:57.360 回答
0

您建议的解决方案对您的阵列进行两次传递。你可以这样做(Linqy):

static bool HasBadData( double[,] doubles )
{
  bool hasBadData = doubles.Cast<double>()
                           .Any( x => double.IsInfinity(x) || double.IsNaN(x) )
                           ;
  return hasBadData ;
}

或者更直接的非 Linq 方式:

static bool HasBadData( double[,] doubles )
{
  bool hasBadData = false ;
  for ( int i = 0 ; !hasBadData && i < doubles.GetLength(0) ; ++i )
  {
    for ( int j = 0 ; !hasBadData && j < doubles.GetLength( 1 ) ; ++j )
    {
      double d = doubles[i,j] ;
      hasBadData = double.IsInfinity(d) || double.IsNaN(d) ;
    }
  }
  return hasBadData ;
}

实际上,它是一个和六个中的六个,尽管我怀疑非 Linq 方法的性能会稍微好一些。

于 2013-11-05T23:31:02.380 回答