【问题标题】:plinq on large lists taking enormous timeplinq 在大型列表上花费大量时间
【发布时间】:2016-10-10 06:33:09
【问题描述】:

我有两个在内存列表中播放,消费者一个有 1500 万个对象,另一个大约 300 万个。

以下是我提出的一些查询..

consumersn=consumers.AsParallel()
                    .Where(w => plays.Any(x => x.consumerid == w.consumerid))
                    .ToList();


List<string> consumerids = plays.AsParallel()
                                .Where(w => w.playyear == group_period.year 
                                         && w.playmonth == group_period.month 
                                         && w.sixteentile == group_period.group)
                                .Select(c => c.consumerid)
                                .ToList();


int groupcount = plays.AsParallel()
                      .Where(w => w.playyear == period.playyear 
                               && w.playmonth == period.playmonth 
                               && w.sixteentile == group 
                               && consumerids.Any(x => x == w.consumerid))
                      .Count();

我使用的是 16 核机器和 32 GB RAM,尽管如此..第一个查询需要大约 20 个小时才能运行..

我是不是做错了什么..

真诚感谢所有帮助。

谢谢

【问题讨论】:

  • Profiler 是您的朋友。但看起来您在此处的第一个查询中执行了 15M * 3M 操作。

标签: c# c#-4.0 plinq


【解决方案1】:

第一个 LINQ 查询效率很低,并行化只能帮到你这么多。

解释:当您编写consumers.Where(w =&gt; plays.Any(x =&gt; x.consumerid == w.consumerid)) 时,这意味着对于consumer 中的每个对象,您可能会遍历整个plays 列表以查找受影响的消费者。因此,最多 300 万消费者乘以 1500 万次播放 = 45 万亿次操作。即使跨 16 个内核,每个内核也有大约 2.8 万亿次操作。

因此,这里的第一步是按照消费者 ID 对所有播放进行分组,并将结果缓存在适当的数据结构中:

var playsByConsumerIds = plays.ToLookup(x => x.consumerid, StringComparer.Ordinal);

那么,你的第一个请求就变成了:

consumersn = consumers.Where(w => playsByConsumerIds.Contains(w.consumerid)).ToList();

这个查询应该更快,即使没有任何并行化。

我无法修复以下查询,因为我看不到您使用 group_period 所做的确切操作,但我建议使用 GroupBy 或 ToLookup 一次性创建所有组。

【讨论】:

  • 谢谢.. 我会在 ToLookup 上阅读。就其他两个查询而言。它们发生在 foreach 循环中,并且没有使用 groupby 子句。在第二个查询中获取消费者 ID 的唯一原因是能够在第三个查询中使用它。那么将第二个查询中的 .Select(c => c.consumerid) 更改为 .ToLookup(x => x.consumerid, StringComparer.Ordinal); 是否正确?和 consumerids.Any(x => x == w.consumerid) 到 consumerids.Contains(w.consumerid)
  • 我的建议是您可以使用GroupBy 来避免第二个和第三个查询的循环。
  • 抱歉,无法理解您使用“GroupBy”避免循环的意思。您能否提供一个示例。第二个和第三个查询中的 where 子句使用来自不同来源的值-group_period 和(期间和组)分别..
【解决方案2】:

第一个查询需要 20 个小时才能运行,因为每次 consumerid 不存在时,plays.Any(x =&gt; x.consumerid == w.consumerid) 需要遍历 15,000,000 次播放的整个列表。

您可以通过在plays 中构造一个包含所有消费者 ID 的哈希集来加快速度,如下所示:

var consumerIdsInPlays = new HashSet<string>(plays.Select(p => p.consumerid));

现在可以重写您的第一个查询以进行 O(1) 查找:

consumersn=consumers
    .AsParallel()
    .Where(w => consumerIdsInPlays.Contains(w.consumerid))
    .ToList();

【讨论】:

  • 我可以使用Hashset进行第二次查询吗?会更快吗?我还应该在第三个查询中将 consumerids.Any(x => x == w.consumerid) 更改为 consumerids.Contains( w.consumerid) 。那会更快吗?发送
  • @Arnab 第二个查询不搜索列表,因此不会更快。如果将consumerids 转换为哈希集,并使用Contains 代替Any,则可以更快地进行第三次查询。您也可以将Where(cond).Count() 替换为Count(cond)。
猜你喜欢
  • 2018-01-10
  • 2015-03-14
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 1970-01-01
  • 1970-01-01
  • 2016-06-21
  • 2016-01-31
相关资源
最近更新 更多