【问题标题】:Poor performance in tree pruning修剪树的性能不佳
【发布时间】:2015-12-27 11:28:58
【问题描述】:

我做了我称之为TreePruner 的东西。其目的:给定从根级别节点列表开始的层次结构,返回一个新的层次结构,其中新的根节点是满足特定条件的最高级别节点。这是我的课。

public class BreadthFirstPruner<TResource>
{
    private IEnumerable<TResource> originalList;
    private IEnumerable<TResource> prunedList;
    private Func<TResource, ICollection<TResource>> getChildren;

    public BreadthFirstPruner(IEnumerable<TResource> list, Func<TResource, ICollection<TResource>> getChildren)
    {
        this.originalList = list;
        this.getChildren = getChildren;
    }

    public IEnumerable<TResource> GetPrunedTree(Func<TResource,bool> condition)
    {
        this.prunedList = new List<TResource>();
        this.Prune(this.originalList, condition);
        return this.prunedList;
    }

    private void Prune(IEnumerable<TResource> list, Func<TResource,bool> condition)
    {
        if (list.Count() == 0)
        {
            return;
        }

        var included = list.Where(condition);
        this.prunedList = this.prunedList.Union(included);
        var excluded = list.Except(included);
        this.Prune(excluded.SelectMany(this.getChildren), condition);
    }
}

这个类做它应该做的,但是它做的太慢了,我不知道为什么。我已经在非常小的层次结构上使用了它,其中完整的层次结构已经在内存中(所以不应该有 linq-to-sql 的惊喜)。但是,无论我多么渴望或多么懒惰,实际计算 linq 表达式结果的第一行代码最终都需要 3-4 秒才能执行。

这是当前正在使用 pruner 的代码:

Func<BusinessUnitLabel, ICollection<BusinessUnitLabel>> getChildren = l => l.Children;
var hierarchy = scope.ToList();
var pruner = new BreadthFirstPruner<BusinessUnitLabel>(hierarchy, getChildren);
Func<BusinessUnitLabel, bool> hasBusinessUnitsForUser = l =>
    l.BusinessUnits.SelectMany(bu => bu.Users.Select(u => u.IDGUID)).Contains(userId);
var labels = pruner.GetPrunedTree(hasBusinessUnitsForUser).ToList();

正如我之前所说,我在执行此操作时使用的数据集非常小。它只有几个级别,大多数级别只有一个节点。正如目前所写的那样,当我调用list.Count() 时,第一次递归 调用Prune 时会出现缓慢,因为那时正在评估层次结构的第二级(excluded.SelectMany(this.getChildren))。

但是,如果我像这样添加.ToList 调用:

var included = list.Where(condition).ToList()

那么缓慢就会在那个时候发生。

我需要怎么做才能让这件事快速进行?

更新

在有人提示我更仔细地重新评估我的状况后,我意识到hasBusinessUnitsForUser 中的那些关联并没有被急切加载。那是有问题的。

【问题讨论】:

  • 你应该选择Recursion 而不是SelectMany 可能需要重写策略,但这肯定会给你一些改进。
  • @dustmouse 这将永远递归,因为list.Where(condition) 在递归调用中总是为空,所以excluded 最终会与传入的列表相同。
  • @vendettamit 我不确定我是否理解您的评论,但尽管如此,我可以通过简单地调用list.Where(condition).ToList() 来导致在调用SelectMany 之前出现缓慢。让我感到困惑的是,鉴于所有对象都已经在内存中,这将是一个缓慢的操作。
  • 你用更简单的条件测试过吗?
  • 查看以下网页上的树形视图是否更好:stackoverflow.com/questions/28976601/…

标签: c# linq tree breadth-first-search


【解决方案1】:

这些调用都是延迟执行的,结果没有被缓存/物化:

    var included = list.Where(condition);
    this.prunedList = this.prunedList.Union(included);
    var excluded = list.Except(included);

即使在这个 sn-p included 中运行两次。由于这是一种递归算法,因此可能会有更多的调用。

ToList 调用添加到任何可能多次执行的序列。

【讨论】:

  • 谢谢。最终我的问题是未能急切加载一些 EF 关联,但我可以看到您的建议如何提高性能。
猜你喜欢
  • 2010-12-24
  • 2012-07-20
  • 1970-01-01
  • 1970-01-01
  • 2012-02-09
  • 2022-07-18
  • 2019-05-22
  • 2014-05-22
相关资源
最近更新 更多