多核编程(erlang 学习笔记)(一)

 对于Erlang来说,有一个巨大的优点,无需任何修改,你的程序就能在n核的CPU上跑得快n倍。 

 但要遵循这些规则

程序要由多个进程构成,这些进程之间彼此不会互相冲突,而且程序逻辑也不存在顺序瓶颈。

在本节中,我们会涉及下面这些话题。
要让程序在多核CPU上运行,需要做些什么?如何并行化顺序代码。
顺序瓶颈问题。
如何避免副作用。
 

1.如何在多核的CPU上更有效率地运行

想要运行得更高效,我们必须努力达到下列标准。

(1)使用大量进程。

(2)避免副作用。

(3)避免顺序瓶颈。

(4)以“少量消息、大量运算”的方式写代码。
如果达到了上面这些标准,我们的Erlang程序就可以在多核CPU上运行得更高效。
 

1.1使用大量进程

要让所有的CPU在所有的时间都在忙碌地工作。达到这-一目标最简便易行的办法就是使用大量的进程。
这里所说的“大量进程”,是相对于CPU的数量的。如果我们有大量的进程,那么就无须担心会有CPU闲着。这看起来是一个纯粹统计学上的结论。如果我们只是少量地使用进程,那么,可能碰巧在单核CPU上会略有优势,但单核CPU的这一微弱优势在成千上百的进程面前就会荡然无存。虽说现在的主流机器中只有为数不多的几个核心,但如果希望程序能够面向未来,那么就需要考虑仅在一个芯片上就有几千个核的工作场景。
进程之间的工作量应该差别不大。如果一个进程要做大量的工作,而另一个进程只做一点点工作,这肯定不是个好主意。
在很多应用程序中,无须精心设计,我们就会有大量的进程。如果应用程序本身就是“内在并发”的,那么我们就无须考虑如何对代码进行并发化的处理。比如说,假如我们在写一个消息系统,它要管理几万个并发连接,那么,从这几万个并发的连接上,
我们可以获得天然的并发特
性。至少,处理每一个连接的代码是无须考虑并发问题的。

1.2顺序瓶颈

 顺序瓶颈是当多个并发的进程需要访问一个顺序的资源时,自然产生的瓶颈,典型的例子就是IO。通常我们只有一个磁盘,所有对这个磁盘的输出最终都是顺序的。要知道,磁盘上只装了一组磁头,我们没法改变这一事实。
每次我们创建并注册一个进程时,我们实际上也创建了一个潜在的顺序瓶颈,所以,尽量避免使用注册进程。如果我们必须要创建这样的注册进程并把它当作服务器来使用,那么就尽量确保它对所有的请求能尽可能快地进行反馈。
通常来说,解决顺序瓶颈的唯一方法是从算法层面着手改进。这通常都不是一件轻松的事,而且往往代价很高。我们必须将一个不是分布式的算法改成一个分布式的算法。关于这个话题(分布算法)学术界已经进行了深入的研究,有大量的研究成果,但在通常的程序设计语言库中却鲜有实际应用。造成这一状况的主要原因是,对于这类算法的需求目前还没有浮现出来。这一情形目前已经发生改变,因为现在我们的编程越来越多涉及网络计算和多核计算环境。

 2.并行化顺序代码

lists:map 它的定义是这样的:

map(_,[]) ->[];
map(F,[H|T]) ->[F(H)|map(F,T)].

可以用一个简单的策略来加快这个顺序化程序的处理速度。我们把这个程序的新版本称作pmap,它对所有参数进行并发求值:
lib_misc.erl

pmap(F, L) ->
  S = self(),
  Ref = er1ang:make_ref(),
  Pids = map(fun(I) ->
    spawn(fun() -> do_f(S, Ref, F, I) end)
             end, L),
  gather(Pids, Ref).
do_f(Parent, Ref, F, I) ->
  Parent ! {self(), Ref, (catch F(I))}.
gather([Pid | T], Ref) ->
  receive
    {Pid, Ref, Ret} -> [Ret | gather(T, Ref)] end;
gather([], _) ->
  [].

pmap能像map一样工作,所不同的是,当我们调用pmap(F,L)的时候,它对L中的每一个参数创建了一个独立的进程进行求值。值得注意的是每一个对L中参数的求值进程,都可能会以任意的顺序结束,很显然它与这个参数在L中的顺序并没有保持对应关系
而用pmap的时候,每一个F(H)都会在它自己的进程中运算,因此,如果我们仍然做相同的动作,那么我们所做的更新会发生在这个新的进程字典上,不会影响到调用pmap这个进程。所以,请小心:有副作用的代码无法简单地通过将map替换为pmap来得到并发化。

什么时候可以用pmap

1.并发粒度

2.不要创建太多的进程

3.在恰当的抽象层次上思考

3.小消息、大计算

我们要利用两个不同的问题集,第一个是这样的:

L = [L1,L2,...,L100],
map(fun lists :sort/1,L)

L当中的每一个元素都是一个包含1000个随机数的列表(我们对它进行排序)。第二个是这样:

L = [27,27,. . .,27],map(fun ptests:fib/1,L)

这里L是一个包含100个27的列表,我们来计算[fib(27),fib(27),...] 100次(fib是斐波那契函数)。

在第一个计算(排序)中,我们使用pmap时会通过消息在进程间发送大量的数据(一个包含1000个随机数的列表),而排序运算实际上是非常迅速的。第二个计算中,我们对每一个进程只需要发送一个很小的请求(计算fib(27)),但计算fib(27)意味着很大的计算量。
在第二个问题中,由于进程之间只有少量的数据复制,却有着相当大的计算量,因此,我们期望第二个问题在多核CPU上会比第一个问题有更明显的性能提升
 

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值