提高LINQ性能

本文关键字:性能 LINQ 提高 | 更新日期: 2023-09-27 18:28:38

我有一个linq语句,如下所示:

var records = from line in myfile 
              let data = line.Split(',')
              select new { a=int.Parse(data[0]), b=int.Parse(data[1]) };
var average = records.Sum(r => r.b)!=0?records.Sum(r => r.a) / records.Sum(r => r.b):0;

我的问题是:记录了多少次。求和(r=>r.b)是在最后一行计算的吗?当LINQ需要计算一个和时,它每次都会循环所有记录吗(在这种情况下,3 sum()所以循环3次)?或者它只是巧妙地在所有记录上循环一次,然后计算所有的总和?


编辑1

  1. 我想知道是否有什么方法可以通过只经历所有的事情来改善它记录只一次(因为我们只需要在一个循环中完成当使用纯for循环时)?

  2. 而且之前确实没有必要将所有内容加载到内存中我们可以求和求平均。我们当然可以求和每一个元素同时从文件中加载。有什么方法可以减少内存吗消费?


编辑2

只是为了澄清一下,我并没有使用LINQ之前,我结束了如上。使用纯while/for循环可以达到所有性能要求。但我后来尝试通过使用LINQ来提高可读性,同时减少代码行数。看来我们不可能同时得到这两者。

提高LINQ性能

两次,像这样写,它将是一次:

var sum = records.Sum(r => r.b);
var avarage = sum != 0 ? records.Sum(r => r.a)/sum: 0;

有很多的答案,但没有一个能概括你所有的问题。

记录了多少次。求和(r=>r.b)是在最后一行计算的吗?

三次。

LINQ每次需要计算时是否都会遍历所有记录求和(在这种情况下,3 sum()循环3次)?

是的。

或者它只是巧妙地在所有记录上循环一次并计算所有记录金额?

没有。

我想知道是否有什么方法可以通过只经历所有的事情来改善它只记录一次(因为当使用普通for循环)?

你可以这样做,但这需要你急切地加载所有与你的下一个问题相矛盾的数据。

在我们可以求和求平均。当然,我们可以在从文件中加载。有什么方法可以减少内存吗消费?

这是正确的。在你最初的文章中,你有一个名为myFile的变量,你正在对它进行迭代,并将它放入一个称为line的局部变量中(读作:基本上是foreach)。由于您没有展示您是如何获得myFile数据的,我假设您正在急切地加载所有数据。

这里有一个懒惰加载数据的快速示例:

public IEnumerable<string> GetData()
{
    using (var fileStream = File.OpenRead(@"C:'Temp'MyData.txt"))
    {
        using (var streamReader = new StreamReader(fileStream))
        {
            string line;
            while ((line = streamReader.ReadLine()) != null)
            {                       
                yield return line;
            }
        }
    }
}
public void CalculateSumAndAverage()
{
    var sumA = 0;
    var sumB = 0;
    var average = 0;
    foreach (var line in GetData())
    {
        var split = line.Split(',');
        var a = Convert.ToInt32(split[0]);
        var b = Convert.ToInt32(split[1]);
        sumA += a;
        sumB += b;
    }
    // I'm not a big fan of ternary operators,
    // but feel free to convert this if you so desire.
    if (sumB != 0)
    {
        average = sumA / sumB;
    }
    else 
    {
        // This else clause is redundant, but I converted it from a ternary operator.
        average = 0;
    }
}

三次,这里应该使用Aggregate,而不是Sum

// do your original selection
var records = from line in myfile 
              let data = line.Split(',')
              select new { a=int.Parse(data[0]), b=int.Parse(data[1]) };
// aggregate them into one record
var sumRec = records.Aggregate((runningSum, next) =>
          { 
            runningSum.a += next.a;
            runningSum.b += next.b;                
            return runningSum;
          });
// Calculate your average
var average = sumRec.b != 0 ? sumRec.a / sumRec.b : 0;

每次对Sum方法的调用都会遍历myfile中的所有行。为了提高性能写:

var records = (from line in myfile 
          let data = line.Split(',')
          select new { a=int.Parse(data[0]), b=int.Parse(data[1]) }).ToList();

因此,它将创建包含所有元素(具有"a"answers"b"属性)的列表,然后对Sum方法的每次调用都将遍历该列表,而无需拆分和解析数据。当然,您可以更进一步,记住Sum方法在某个临时变量中的结果。

james,我根本不是专家,这是我的主意。我认为它可能会减少到1。也许还有更多的代码。records仍然是AnonymousType{int a,int b}的IEnumerable。

*动态是一种快速解决问题的方法。你应该为它写一个结构。

int sum_a = 0,sum_b = 0;
Func<string[], dynamic> b = (string[] data) => { 
    sum_a += int.Parse(data[0]); 
    sum_b += int.Parse(data[1]);
    return new {a = int.Parse(data[0]),b = int.Parse(data[0]) }; 
};
var records = from line in fileLines 
              let data = line.Split(',')
              let result = b(data)
              select new { a = (int)result.a, b = (int)result.b };
var average = sum_b != 0 ? sum_a / sum_b : 0;

对于其他结构,它很简单。

public struct Int_Int //May be a class or interface for mapping
{
    public int a = 0, b = 0;        
}

然后

int sum_a = 0,sum_b = 0;    
Func<string[], Int_Int> b = (string[] data) => { 
    sum_a += int.Parse(data[0]); 
    sum_b += int.Parse(data[1]);
    return new Int_Int() { a = int.Parse(data[0]), b = int.Parse(data[0]) }; 
};
var records = from line in fileLines
              let data = line.Split(',')
              select b(data);
var average = sum_b != 0 ? sum_a / sum_b : 0;

SUM在您调用它的任何时候都会获取所有记录,我建议您使用ToList()-->您使用ToList[()吗?

var records = from line in myfile 
              let data = line.Split(',')
              select new { a=int.Parse(data[0]), b=int.Parse(data[1]) }.ToList();
var sumb = records.Sum(r => r.b);
var average = sumb !=0?records.Sum(r => r.a) / sumb :0;