我在.NET 4.51,C#Web服务上有一个工作单元,需要100毫秒。通常,Web请求包含10个单位的工作。因此,通过for循环顺序处理它需要大约一秒钟。
foreach (var u in unitsOfWork) {
Run(u);
}
由于该盒子有12个CPU,我决定将工作分开并并行运行,希望获得性能提升。我使用Parallel.ForEach
来完成工作:
Parallel.ForEach(unitsOfWork,u => {
Run(u);
});
令我惊讶的是,每个工作单元平均花费425毫秒。所以最后我节省了约500毫秒的请求。看起来我应该能够获得更好的性能,看看这个盒子有12个CPU ......我错过了一些简单的东西吗?
我寻找任何共享的东西(可能会阻止它),但什么都没找到......所以我试着尝试。我发出了2个工作单元的请求,每个工作大约需要125毫秒。有3个请求,每个单元需要150毫秒,依此类推。随后每个单位的数量,罚款约为25至30毫秒。
所以我要么做错了......或者多线程只是固有的开销(没有意识到这很大)。
P.S。我也尝试用Thread.Join替换Parallel.For - 结果相同。
答案 0 :(得分:6)
您可以实现的理论speedup由Amdahl's law:
管理
其中T(1)
是单线程速度,n
是CPU的数量,B
是无法序列化的任务的百分比。通过该公式,启动新任务的开销被认为是零。
如果您的任务完全可并行化,B
将为零,您将在1/12的时间内完成任务。然而,即使是适度的B
,例如20%,也会将12个CPU的最高潜在加速限制为仅3.75倍 - 略高于理论极限12倍的三分之一。
无法并行化的事情包括对共享资源(如I / O)的序列化访问以及等待完成其他任务。
处理缓存争用会让事情变得更糟:当并发任务访问不同的内存区域时,它们会将相互之间的数据从硬件缓存中踢出,这相当于上面公式中B
的增加。
总而言之,您的观察并不罕见,并且您没有遗漏任何东西。实现理论上可能的sppedup非常困难,实现的实际加速取决于并行程序需要运行的任务。
答案 1 :(得分:2)
看起来我应该能够获得更好的性能,看看这个盒子有12个CPU ......我错过了一些简单的东西吗?
是的,你的工作不受CPU限制。您正在执行网络请求。这是IO约束的工作。瓶颈不在于CPU执行操作所花费的时间,而在于您的网络连接。显然,它可以通过一次执行多个请求来增加吞吐量,但它只能在连接饱和之前扩展太多。这一点与您拥有的CPU数量无关。
请注意,这里根本不需要多个线程来实现并行性。您可以简单地异步创建一些Web服务请求,然后在启动所有异步请求之后等待所有异步请求完成并行化操作,同时只有单个线程。假设您对Web服务调用的结果所做的实际CPU绑定工作并不重要,这甚至可以通过在仍然并行化大部分工作的同时消除线程的所有开销来提高性能。
答案 2 :(得分:1)
您提到您的一些工作涉及在集合之间移动数据。这些集合是否在线程之间共享?读/写这些集合时你会拿锁吗?如果是这样(并且具体取决于你需要对这些集合做什么),你可以通过更多" chunky"来获得更好的规模。与您的收藏 - 有没有办法安排您的工作,使您对集合的读/写都发生在线程完成之前/之后?