我问了一个问题并得到了回答 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 就像这个链一样工作——:
因此,我们不能开始枚举直到 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 行)。)
这里是 5m 列表:http://i.stack.imgur.com/DflGR.jpg (第一个是 withBatchify ,另一个不是)
最佳答案
重要提示:显示的图像包括 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/