您更喜欢“实现” IEnumerables?

时间:2018-06-19 12:22:45

标签: c# performance linq memory functional-programming

有时有必要在方法中间实际“评估” IEnumerable,因为它在多个查询中使用并且编译器发出警告(“可能的IEnumerable多重枚举”)

var skippedIds = objects.Where(x => x.State=="skip")
                .Select(x => x.Id)
                .Distinct();

            var skippedLookup = skippedIds.ToLookup(x => x.FundId, _ => new { _.Id, _.Name});

            if (skippedIds.Any()) // compiler warning
            {
                ...
                // other iterations over skippedIds, etc.
            }

我曾经做过:

var skippedIds = objects.Where(x => x.State=="skip")
                    .Select(x => x.Id)
                    .Distinct()
                    .ToList();
...

,但想知道是否有更好的选择。上面的代码在堆上创建List<T>对象,我猜这是在方法中死亡的临时变量的上下文中不必要的GC负担。 我现在正在使用ToImmutableArray()库随附的System.Collections.Immutable不仅会创建堆栈分配的对象不正确,谢谢注释),而且还会在我的代码中附加“不可变”语义,这是一种很好的功能风格练习。

但是性能影响是什么?在一个方法的本地多个地方“物化”临时子查询结果的首选方式是什么?

2 个答案:

答案 0 :(得分:3)

在内存中实现它的性能含义是:

  • 数据库中所有项目的最初抓取-如果您不打算使用所有项目,那么您可能会花更多的钱。
  • 根据您使用的结构,您可能需要插入费用-ToImmutableArray()的费用大约是ToArray(),因为ImmutableArray仅包装内置数组类型并删除了突变选项。
  • 如果您要快速丢掉物体,GC负担就不用担心了。因为该项目从Gen 0跳到Gen 1的可能性很小,而且无需花费太多就可以收集。但是显然,您分配的对象越多,触发集合的可能性就越大。

您可以使用language-ext中的Seq<A>类型(公开:我是作者)。之所以被设计为“更好的可枚举”,是因为它只会消耗一次IEnumerable<A>中的每个项目,并且像IEnumerable<A>一样懒惰。

因此,您可以这样做:

var skippedIds = objects.Where(x => x.State=="skip")
                        .Select(x => x.Id)
                        .Distinct()
                        .ToSeq();

显然,这个世界上没有免费的东西,Seq<A>的成本是:

  • 每个消耗项的分配(因为它会记住您已阅读的项,因此您无需再次执行该分配)。但是它们是微小的对象,只有两个引用,因此对GC的压力很小。
  • 打开与数据库的连接的时间超出您的可能需要的时间,这可能会导致数据库的其他性能问题:死锁等。

但是好处是您只消耗所需的东西,而只消耗一次。就我个人而言,我希望限制您的查询并使用ToImmutableArray(),从数据库中获取少于您所需的资源将永远是首选方法。

答案 1 :(得分:0)

在这种情况下,问题是您已经实现了结果(以Lookup的形式),但随后引用了未实现结果。

var skippedIds = objects.Where(x => x.State=="skip")
    .Select(x => x.Id)
    .Distinct();

var skippedLookup = skippedIds.ToLookup(x => x.FundId, _ => new { _.Id, _.Name});

if (skippedIds.Any()) // compiler warning

在上面的代码中,skippedIds未实现,但skippedLookup已实现。因此,您可以考虑更改:

if (skippedIds.Any()) // compiler warning

收件人:

if (skippedLookup.Any()) // no compiler warning


如果我们采用更一般的情况,则需要一些其他指导:

  • 考虑多次枚举(例如两次访问数据库)与实现(例如RAM使用)的性能成本-最好是上下文相关的
  • 考虑使用ToListToImmutableArray来实现(两者看起来都表现不错)。
  • 考虑是否可以从代码中删除任何LINQ操作而不影响整体功能。一个常见的错误是先使用Any然后使用foreach-在许多情况下,Any可以删除,因为如果foreach自动不执行任何操作,枚举为空。
  • 如果IEnumerable使用LINQ to Objects并且您正在执行Distinct,则执行实例​​化操作(例如ToList),然后改为使用new HashSet<YourTypeHere>(YourEnumerableHere)。一键执行Distinct和物化操作。
  • 使用ToList实现时,请考虑将生成的List暴露为IReadOnlyList,以向消费者表明它并非旨在更改。
  • 实际上,选择哪种方法并不重要。当然,List及其底层数组会产生一些GC开销。但是,在整体GC负载(例如List所包含的对象)的更广泛上下文中,这不太可能成为问题。如果列表足够大,则可以使用大对象堆,这不是最佳选择。但老实说,让GC做好工作。如果有问题,请先优化,然后再优化。