【问题标题】:Out of Memory Lambda Compile versus inline delegates内存不足 Lambda 编译与内联委托
【发布时间】:2014-12-10 14:59:44
【问题描述】:

将 4.5.1 与一个应用程序一起使用,该应用程序在服务器端通过多个 REST 请求同时对图表数据进行混洗。

使用 IQueryable 构建查询。例如,我最初有以下几点:

 var query = ctx.Respondents
   .Join(
     ctx.Respondents,
     other => other.RespondentId,
     res => res.RespondentId,
     (other, res) => new ChartJoin { Respondent = res, Occasion = null, BrandVisited = null, BrandInfo = null, Party = null, Item = null }
   )
   . // bunch of other joins filling out the ChartJoin
   .Where(x => x.Respondent.status == 1)
   . // more Where clauses dynamically applied
   .GroupBy(x => new CommonGroupBy { Year = (int)x.Respondent.currentVisitYear, Month = (int)x.Respondent.currentVisitMonth })
   .OrderBy(x => x.Key.Year)
   .ThenBy(x => x.Key.Month)
   .Select(x => new AverageEaterCheque
     {
       Year = x.Key.Year,
       Month = x.Key.Month,
       AverageCheque = (double)(x.Sum(m => m.BrandVisited.DOLLAR_TOTAL) / x.Sum(m => m.BrandVisited.NUM_PAID)),
       Base = x.Count(),
       Days = x.Select(m => m.Respondent.visitDate).Distinct().Count()
   });

为了允许动态分组(通过客户端),GroupBy 是使用返回字典的 C# 表达式生成的。 Select 也必须使用表达式生成。上面的 Select 变成了这样:

 public static Expression<Func<IGrouping<IDictionary<string, object>, ChartJoin>, AverageEaterCheque>> GetAverageEaterChequeSelector()
 {
    // x => 
    var ParameterType = typeof(IGrouping<IDictionary<string, object>, ChartJoin>);
    var parameter = Expression.Parameter(ParameterType);

    // x => x.Sum(m => m.BrandVisited.DOLLAR_TOTAL) / x.Sum(m => m.BrandVisited.NUM_PAID)
    var m = Expression.Parameter(typeof(ChartJoin), "m");

    var mBrandVisited = Expression.PropertyOrField(m, "BrandVisited");

    PropertyInfo DollarTotalPropertyInfo = typeof(BrandVisited).GetProperty("DOLLAR_TOTAL");
    PropertyInfo NumPaidPropertyInfo = typeof(BrandVisited).GetProperty("NUM_PAID");

    ....

    return a lambda...
 }

当我在本地进行测试运行时,出现内存不足错误。然后我开始阅读 Totin 和其他 Lambda 编译的博客,一般来说,表达式树很昂贵。不知道它会破坏我的应用程序。而且我需要动态添加分组的能力,这导致我将表达式树用于 GroupBy 和 Select 子句。

想要一些关于如何在我的应用程序中追踪内存违规者的指示吗?已经看到有些人使用 dotMemory,但也可以使用一些实用技巧。很少有监控C#、DotNet的经验。

【问题讨论】:

  • 我将首先检查您的 EF 查询返回的数据。这更有可能是内存猪而不是表达式。
  • 您显示的代码根本没有使用您正在构建的表达式。我们怎么能告诉您您没有向我们展示的代码有什么问题?
  • @Servy,问题显示了硬编码的 GroupBy 和 Select 子句编写为内联 lambda,然后是使用表达式编写 Select 子句的简短示例。那次切换是我注意到内存问题的时候。
  • @user1620220,我下载了 dotMemory 并进行了分析。看起来 String 和 Dictionary 对象正在消耗第 2 代。也许 Dictionary(从 GroupBy 传递到 Select)不是“动态”结构的好选择?
  • 第 2 代的利用率为 99%。必须阅读 C# 堆。

标签: c# performance linq lambda expression-trees


【解决方案1】:

由于您将表达式编译为委托,因此该操作是使用 LINQ to Objects 执行的,而不是使用 IQueryable 重载。这意味着整个数据集都被拉入内存,所有处理都由应用程序完成,而不是在数据库中完成处理,只有最终结果被发送到应用程序。

显然,将整个表拉入内存就足以让您的应用程序在内存不足的情况下运行。

您需要编译 lambda,并将其保留为表达式,从而允许查询提供程序将其转换为 SQL,就像使用原始代码所做的那样。

【讨论】:

  • 在我的要点的第 20 行,我可以删除 GroupBy 子句中的编译调用。但是,我无法从第 28 行的 Select 子句中删除它。这就是 Compile 到处乱扔的原因。我的方法签名有问题吗?
  • @MatthewYoung 不编译任何表达式非常重要。通过将它们编译为委托,您可以强制将代码转换为 LINQ-to-Objects。你需要它来使用 EF,并被翻译成 SQL 代码。如果您编译表达式,则不会发生这种情况。
  • 我认为你已经解决了根本问题。从 Group By 中删除 Compile() 时出错,因为 LINQ to Entities 无法识别我的字典对象,并且该方法无法转换为存储表达式。
  • 当我说我“不能”从第 28 行删除 Compile 调用时,编译器说 GetAveragePartySizeSelector() 在 Select 子句中无效。
  • @MatthewYoung 那么你需要找出原因。在这种情况下,错误消息非常清楚。你没有IQueryable,你有一个IEnumerable,这意味着你正在做一些更早的事情来将你的查询转换为内存中的序列。您的方法使用IEnumerable,而不是IQueryable,从而确保您的所有操作都作用于内存中的集合。
猜你喜欢
  • 1970-01-01
  • 2020-08-17
  • 2014-01-30
  • 2019-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-10
相关资源
最近更新 更多