c# - Batchify 长 Linq 操作?

标签 c# performance linq

我问了一个问题并得到了回答 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*/    }

Here, the purpose of Batchify is to ensure that we aren't helping the server too much by taking appreciable time between each operation - the data is invented in batches of 1000 and each batch is made available very quickly.

现在,我了解它在做什么,但我不能分辨出区别,因为我可能会错过它实际工作的方式。 (有时您认为自己知道某事...直到...)

好的,回到基础:

AFAIK,Linq 就像这个链一样工作——:

enter image description here

因此,我们不能开始枚举直到 select 的结果:

Where-->OrderBy-->Select 

完成了。

所以基本上我等待 select所有正确的数据(之后 where ,在 orderby 之后),只有这样 - 我的代码才能触及这些值。 (从 select 产生)

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

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

问题

  • 有人能给我点灯吗?这是如何工作的?

注意

我已经知道(例如)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 秒,100 万 项。 9 秒,500 万 项。

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

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

enter image description here

最佳答案

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

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

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

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

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

现在考虑 batchify 做了什么:

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

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

关于c# - Batchify 长 Linq 操作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23626695/

相关文章:

c# - 如何查找具有给定名称的最高级别后代

c# - 为什么在可以使用 "as"时还要强制转换引用类型?

c# - 格式十进制扩展的 NUnit 测试中的实际值和预期值不相同

c# - Ivona 请求签名问题 - 签名不匹配(AWS 签名版本 4)

python - 为什么这两种计算总和的方法会产生不同的运行时间

html - 如何解决 IE 中的渲染性能问题

c# - gridview 上的复选框事件?

ruby-on-rails - Rails 是确定变量范围的最佳方式

c# - (更有可能)一个非常微妙的行为的迭代器错误?

c# - 编写将为每个列表元素添加值的 LINQ 语句