批量化长Linq操作?

我问了一个问题,并在这里得到关于性能问题的答案,我收集了大量数据。 (用linq创建)

好吧,让我们把它放在一边。

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

 /*1*/ static IEnumerable Batchify(this IEnumerable source, int count) /*2*/ { /*3*/ var list = new List(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拥有所有正确的数据( 在其后 ,在orderby之后), 然后 – 我的代码可以触摸这些值。 (从select产生)

但根据我对马克答案的理解,似乎这些yields之间存在差距,允许其他资源做某事……(?)

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

  • 有人可以点光吗? 这是如何运作的 ?

NB

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

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

但如果是这样,我的代码无法触摸它,直到计算出所有值(在whereorderby )…

编辑:

对于那些询问是否存在差异的人: http : //i.stack.imgur.com/19Ojw.jpg

1M项目需要2秒。 5M项目为9秒。

(忽略第二行时间,(额外的console.write行)。) 在此处输入图像描述

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

在此处输入图像描述

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

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

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

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

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

现在考虑一下batchify的作用:

  • 创建一个项目(CPU工作)并缓冲它
  • 创建一个项目(CPU工作)并缓冲它
  • 创建一个项目(CPU工作)并缓冲它
  • 产生一个项目
  • 将其发送到商店(网络IO)
  • 产生一个项目
  • 将其发送到商店(网络IO)
  • 产生一个项目
  • 将其发送到商店(网络IO)
  • 创建一个项目(CPU工作)并缓冲它

在这里,您可以看到存储请求之间的CPU工作量显着减少。 这更正确地模仿原始代码,其中最初创建了数百万的列表,然后进行迭代。 但另外,这意味着创建出站消息的线程很可能至少与编写器线程一样快 ,这意味着出站队列在任何可观的时间内都不可能变为零。 这样可以实现更低的数据包碎片,因为现在不是每个请求都有一个数据包,每个数据包中都有很多消息。 较少的分组通常意味着由于减少的开销而导致更高的带宽。

我知道这个解决方案是由一个可能比我更了解的用户发布的,但坦率地说,在你的例子中,它没有做任何事情 。 你在上一篇文章中的真正杀手是你在这个物化集合上启动foreach循环之前使用List<> 在内存中实际创建了 10M条目。 现在你正在使用一个IEnumerable<> ,它不会在内存中同时创建10M,但是一个接一个(如果是并行的话可能更多)。 Batchify方法很不错……但如果你跳过它,它应该工作相同。 最好的情况,这是一个微优化。