TL;博士
当我以更高的并发性执行CPU密集型任务时(例如,10K同时对4),我的erlang程序的性能会有所提高。为什么呢?
我正在使用erlang编写map reduce框架,我正在进行性能测试。
我的地图功能是高度CPU密集型的(主要是纯计算)。它还需要访问一些静态数据,因此我在我的机器上有一些持久性(延迟,即通过应用程序。生命周期生活)工作进程,每个工作进程都在内存中,并等待映射请求。 map的输出被发送到管理器进程(向工作人员发送映射请求),其中执行reduce(非常轻量级)。
无论如何,当我立即为工作人员收到的每个地图请求生成一个新进程时,我注意到我的吞吐量越来越高,而不是让工作进程本身逐个地同步执行地图请求(从而在其进程队列中留下一堆映射请求,因为我一次性触发了映射请求。
代码段:
%% When I remove the comment, I get significant performance boost (90% -> 96%)
%% spawn_link(fun()->
%% One invocation uses around 250ms of CPU time
do_map(Map, AssignedSet, Emit, Data),
Manager ! {finished, JobId, self(), AssignedSet, normal},
%% end),
与在紧密循环中执行相同计算的情况相比,使用“立即生成”方法获得96%的吞吐量(效率)(例如,10000个地图减少作业完全并行运行)。当我使用“工人逐个执行”的方法时,我只得到90%左右。
我知道Erlang应该擅长并发的东西,即使我一次执行10K map reduce请求而不是100等,效率也不会改变,我印象深刻!但是,由于我只有4个CPU内核,如果我使用较低的并发性(如4或5),我希望能获得更好的吞吐量。
奇怪的是,我的CPU使用率在两种不同的实现中看起来非常相似(几乎完全与所有内核的100%挂钩)。性能差异非常稳定。即即使我只做100个地图减少工作,我仍然使用“立即生成”方法获得96%左右的效率,当我使用“逐个”方法时,效率大约为90%。同样,当我测试200,500,1000,1000个工作时。
我首先怀疑在工作进程队列中排队是罪魁祸首,但即使我在工作进程队列中只有25条消息,我仍然看到性能较低。 25个消息似乎非常小,导致阻塞(我正在进行选择性消息匹配,但不是过程必须将消息放回队列)。
我不知道该如何从这里开始。我做错了什么,还是我完全错过了什么?
更新
我做了一些测试,发现性能差异可能会根据条件消失(尤其是我将静态数据划分为多少个工作进程)。看起来我还有很多东西需要学习!
答案 0 :(得分:3)
假设1个工作流程有3个地图操作,我们有第一个变体:
_______ _______ _______
| m | | m | | m |
| | | | | |
_| |_| |_| |_
a a a r
其中a
是管理任务(从消息队列中读取,调度地图等)m
是实际地图,r
正在发回结果。第二个变体,为每个地图生成一个过程:
_________________._
| m r
| ___________________._
| | m r
| | _____________________._
_|_|_| m r
a a a
正如您所看到的,管理任务(a
)与地图(m
)同时发生,并且与发送结果的时间相同(r
)
这将使CPU一直忙于地图(即计算密集型)工作,而不是偶尔进行短暂的下降。这很可能是您在吞吐量中看到的微小增益。
由于您从一开始就拥有相当高的并发性,因此您只能看到吞吐量相对较小的增益。相比之下,理论上只运行一个工作流程(如第一个版本),你会看到更大的收益。
答案 1 :(得分:1)
首先,我想说这是一个非常有趣的问题。我想给你一些提示:
由于 reduce ,每个运行队列(shell中的[rq:x])发生任务切换:如果Erlang进程调用BIF或用户定义的函数,则会增加它的< em>减少计数器。在一个进程中运行CPU密集型代码时,它会经常增加它的减少计数器。当减少计数器达到某个阈值时,将发生过程切换。 (因此,一个具有较长生命周期的进程具有与具有较短生命周期的多个进程相同的开销:它们都具有“相同”的总减少计数器并在达到阈值时触发它,例如一个进程< / em>:50,000减少,更多进程:5 * 10,000减少= 50,000减少。)(运行时原因)
在4核与1核上运行有所不同:但是,时间是不同的。核心处于100%的原因是因为一个或多个核心正在进行映射,而其他核心正在“填充”您的消息队列。当您生成映射时,“填充”消息队列的时间更少,有更多时间进行映射。显然,映射是一种比填充队列更昂贵的操作,并为其提供更多内核,从而提高了性能。 (时间/调整原因)
当您增加并发级别,进程正在等待(接收/调用OTP服务器/等)时,您将获得更高的吞吐量。例如:从静态持久化工作者请求数据需要一些时间。 (语言原因)