【发布时间】:2011-10-13 10:53:57
【问题描述】:
tl;dr
当我以更高的并发性(例如 10K 与 4 次)执行 CPU 密集型任务时,我的 erlang 程序的性能会更好。为什么?
我正在使用 erlang 编写 map reduce 框架,并且正在进行性能测试。
我的地图函数是高度 CPU 密集型的(主要是纯计算)。它还需要访问一些静态数据,所以我在我的机器上有一些持久的(挥之不去的,即在应用程序的生命周期中存在)工作进程,每个进程都有一部分数据在内存中,并等待地图请求。 map 的输出被发送到 manager 进程(它向 worker 发送 map 请求),在那里执行 reduce(非常轻量级)。
无论如何,我注意到,当我立即为工作人员收到的每个地图请求生成一个新进程时,我的吞吐量得到了提高,而不是让工作人员进程自己一个接一个地同步执行地图请求(因此在其进程队列中留下了一堆地图请求,因为我一次触发了所有地图请求)。
代码sn-p:
%% 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),
与我在紧密循环中执行相同计算时相比,使用“立即生成”方法(例如,10000 个完全并行运行的 map reduce 作业)可以获得 96% 的吞吐量(效率)。当我使用“工人一个接一个”的方法时,我只得到了大约 90%。
我知道 Erlang 应该擅长并发处理,我印象深刻的是,即使我一次执行 10K 个 map reduce 请求而不是 100 个等,效率也不会改变!但是,由于我只有 4 个 CPU 内核,如果我使用 4 或 5 等较低的并发性,我希望获得更好的吞吐量。
奇怪的是,我的 CPU 使用率在 2 个不同的实现中看起来非常相似(在所有内核上几乎完全固定为 100%)。性能差异相当稳定。 IE。即使我只做 100 个 map reduce 工作,使用“立即生成”方法的效率仍然约为 96%,而使用“一对一”方法时的效率约为 90%。同样,当我测试 200、500、1000、10K 作业时。
我首先怀疑工作进程队列中的排队是罪魁祸首,但即使我应该在工作进程队列中只有 25 条消息,我仍然看到性能较低。 25 条消息对于造成阻塞来说似乎很小(我正在做选择性的消息匹配,但不是进程必须将消息放回队列的方式)。
我不确定我应该如何从这里开始。我做错了什么,还是我完全错过了什么?
更新
我做了更多的测试,发现性能差异可能会根据条件消失(特别是我划分静态数据的工作进程数)。看来我还有很多东西要学!
【问题讨论】:
标签: performance optimization concurrency erlang