【问题标题】:Batchify long Linq operations?批处理长 Linq 操作?
【发布时间】:2014-05-13 08:52:29
【问题描述】:

我问了一个问题,here 回答了我在大量 数据集合中遇到的性能问题。 (用 linq 创建)

好吧,我们先放一边。

但 Marc 建议的一项有趣(并且天才)优化是 Batchify linq 查询。

/*1*/ static IEnumerable<T> Batchify<T>(this IEnumerable<T> source, int count)
/*2*/    {
/*3*/      var list = new List<T>(count);
/*4*/      foreach(var item in source)
/*5*/         {
/*6*/           list.Add(item);
/*7*/           if(list.Count == count)
/*8*/             {
/*9*/               foreach (var x in list) yield return x;
/*10*/              list.Clear();
/*11*/            }
/*12*/        }
/*13*/      foreach (var item in list) yield return item;
/*14*/    }

在这里,Batchify 的目的是确保我们不会帮助 通过在每次操作之间花费大量时间来服务器过多 - 数据是按 1000 个批次发明的,每批都是制作的 很快就能买到。

现在,我了解它在做什么,但我无法分辨出区别,因为我可能错过了它的实际工作方式。 (有时你认为你知道一些事情......直到......

好的,回到基础:

AFAIK ,Linq 像这条链一样工作 - :

所以,我们不能开始枚举直到select in 的结果:

Where-->OrderBy-->Select 

完成了。

所以基本上我 等待 select 获得 所有 正确数据(之后 where 之后 orderby) ,并且只有这样 - 我的代码可以触及这些值。 (来自select

但根据我对Marc的回答的理解,那些yields之间似乎有一个差距,允许其他资源做某事......(?)

如果是这样,那么在 #4 的每次迭代之间,在 line#9 之后,CPU 有时间做其他事情吗?

问题

  • 有人可以帮忙吗?这是如何工作的?

nb

我已经知道(例如)select 只不过是:

public static IEnumerable<TResult> Select<TSource,TResult>
 (this IEnumerable<TSource> source, Func<TSource,TResult> selector)
{
   foreach (TSource element in source)
       yield return selector (elem![enter image description here][3]ent);
}

但如果是这样,我的代码在计算所有值(where 之后,orderby 之后)之前无法触及它......

编辑:

对于那些询问是否有区别的人:http://i.stack.imgur.com/19Ojw.jpg

2 秒用于 1M 个项目。 5M9 秒。

(忽略时间的第二行,(额外的console.write行)。)

这里是 5m 列表:http://i.stack.imgur.com/DflGR.jpg(第一个是 withBatchify,另一个不是)

【问题讨论】:

  • 我喜欢用图片提问。它对读者有很大帮助。

标签: c# performance linq


【解决方案1】:

重要提示:显示的图像包含OrderBy:您应该注意这里breaks 批处理,因为OrderBy 是一个缓冲运算符。我展示的批处理方法适用于非缓冲假脱机流。

在我使用它的上下文中,原点(在批处理之前)是一个迭代器块,它在每次迭代时做了很多涉及对象创建和伪随机数生成器的事情。因为有问题的代码对时间敏感,所以我想要做的是在每次调用存储之间引入一个可靠的暂停(用于创建每个项目的 CPU 工作)。这部分是为了模拟原始代码,它预先创建了所有对象,部分是因为我了解 SE.Redis 如何处理套接字工作。

让我们考虑没有Batchify 的行为:

  • 创建一个项目(CPU 工作)并生成它
  • 发送到商店(网络IO)
  • 创建一个项目(CPU 工作)并生成它
  • 发送到商店(网络IO)
  • 创建一个项目(CPU 工作)并生成它
  • 发送到商店(网络IO)
  • ...

特别是,这意味着存储请求之间存在可预测的暂停。 SE.Redis 在专用工作线程上处理套接字 IO,上述情况很容易导致高数据包碎片,特别是因为我使用了“即发即弃”标志。写入器线程需要定期刷新,当或者缓冲区达到临界大小,出站消息队列中没有更多工作时,它会这样做。

现在考虑一下 batchify 的作用:

  • 创建一个项目(CPU 工作)并缓冲它
  • 创建一个项目(CPU 工作)并缓冲它
  • ...
  • 创建一个项目(CPU 工作)并缓冲它
  • 交出一件物品
  • 发送到商店(网络IO)
  • 交出一件物品
  • 发送到商店(网络IO)
  • ...
  • 交出一件物品
  • 发送到商店(网络IO)
  • 创建一个项目(CPU 工作)并缓冲它
  • ...

在这里,您有望看到存储请求之间的 CPU 工作量显着减少了。这更正确地模仿了最初创建数百万列表然后迭代的原始代码。但此外,这意味着创建出站消息的线程很有可能至少与写入线程一样快,这意味着出站队列不太可能变为零时间。这样可以大大降低数据包碎片,因为现在每个请求都没有一个数据包,每个数据包中很有可能包含多个消息。由于开销减少,更少的数据包通常意味着更高的带宽。

【讨论】:

  • 无法要求更多。谢谢你。 (p.s. 不提 IO 上下文是我的错,但我误解了 Batchify 是为了解决 IO 延迟间隔而发明的)。
【解决方案2】:

我知道这个解决方案是由一个可能比我知识渊博的用户发布的,但坦率地说,在您的示例中,它并没有做任何事情。在您上一篇文章中真正的杀手是您使用List&lt;&gt; 实际上创建 10M 条目在内存中,然后开始这个物化集合的foreach 循环。现在您正在使用IEnumerable&lt;&gt;,它不会在内存中同时创建 10M,而是一个接一个(如果并行可能更多)。 Batchify 方法很好......但如果你跳过它,它应该同样有效。最好的情况,这是一个微优化。

【讨论】:

  • Marc 通过 EACH TASK 创建它自己的列表解决了 10m 的问题。这是因为什么都没有开始,因为我们首先计算了10m(这是个坏主意)。它仍然没有回答上面的问题。 (我不是反对者)
  • 你能发布一个独立的例子吗?使用 Batchify 比不使用 Batchify 产生更好的结果?因为坦率地说,你似乎明白是什么让最后一个答案有效。
  • 再次 - 我试图理解的所有内容(很简单)是 marc 所说的以及 如何 它是如何工作的 —— : 这里, Batchify 的目的是通过在每次操作之间花费可观的时间来确保我们不会对服务器提供太多帮助 - 数据以 1000 个批次为单位创建,并且每个批次都非常快速地可用。
  • 那你在评论中问马克怎么样?因为如果我告诉你它没有做你认为它做的事情,你无论如何都不会相信我。
  • 我使用它的场景不涉及列表中的 10M 条目。我将在答案中解释更多。
猜你喜欢
  • 1970-01-01
  • 2018-05-19
  • 2011-06-04
  • 2012-01-29
  • 1970-01-01
  • 1970-01-01
  • 2019-12-13
  • 2020-05-08
  • 2014-07-02
相关资源
最近更新 更多