批量化长Linq操作?

时间:2014-05-13 08:52:29

标签: c# performance linq

我问了一个问题并得到了here关于性能问题的回答,这些问题与大型数据集合有关。 (用linq创建)

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

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

Batchify
  

这里,Batchify的目的是确保我们没有帮助   服务器过多,每次操作之间花费相当多的时间 -   数据是以1000个批次发明的,每批都是制造出来的   非常快。

现在,我理解它正在做什么,但我无法分辨出差异,因为我可能会错过它实际工作的方式。 (有时候你认为你知道的事情......直到...

好的,回到基础:

AFAIK,Linq就像这个链一样 - :

enter image description here

因此,我们无法开始枚举直到 /*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*/ } 的结果:

select

完成了。

所以基本上我等待Where-->OrderBy-->Select 所有正确的数据( select之后,之后where)和只有 - 我的代码可以触及这些值。 (来自orderby

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

如果是这样,那么在#4的每次迭代之间,在行#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);
}

但如果是这样,我的代码无法触及它,直到计算出所有值(whereorderby之后)...

编辑:

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

1M 项目的

2 秒。 5M 9 秒。

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

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

enter image description here

2 个答案:

答案 0 :(得分:3)

重要提示:显示的图片包含OrderBy:您应该注意此在此处中断 batchify,因为OrderBy是一个缓冲的运算符。我展示的batchify方法适用于非缓冲假脱机流。

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

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

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

特别是,这意味着商店请求之间存在可预测的暂停。 SE.Redis在专用的工作线程上处理套接字IO,上面的内容很容易导致高分组碎片,特别是因为我使用的是&#34; fire并且忘了&#34;旗。编写器线程需要定期刷新,当 缓冲区达到临界大小时,它会在出站消息队列中再没有工作。

现在考虑一下batchify的作用:

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

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

答案 1 :(得分:0)

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