Erlang实战:并行枚举排序
这是海量数据的时代!互联网每天产生的数据量远远超出了我们所能想象的范围,无论是国外的Facebook、Twitter,还是国内的微博、人人,还有各种电商们,这些互联网企业在数据上是富有的,它们掌握着海量的用户数据,同时它们也需要对这些海量数据进行分析和处理。我们以前的串行化算法似乎显得力不从心,一个是计算机本身就设计为多核的,它们存在并行化问题;另一方面,更多的计算机加入到并行的行列,并行化正成为一种潮流和趋势,因为它们能用空间换取时间和性能,或者说用更多的机器来进行分析计算。
我知道的常用的并行计算工具大致有三种:MapReduce、MPI,还有就是本文讨论的Erlang。MapReduce最早是google设计并实施的,现在已经成为云计算中的一个火热的技术,它通过一个映射(Map)和规约(Reduce)来进行并行数据处理,实际上它是一个实时批处理再加上一些容错处理机制来保证系统的性能和可靠性;MPI我不太熟悉,它是一个库,不是一门语言,一般与C/FORTRAN结合使用;Erlang是一个基于消息传递的并发编程语言,正因为它是基于消息的,因此没有共享内存和锁机制,因此Erlang比较简单,它是函数型编程语言,我们在编写程序时写的都是函数。
下面我将通过一个排序实例:枚举排序来讲述Erlang并行编程!
实战:并行枚举排序
我们先来看看其概念:枚举排序也叫秩排序(Rank Sort),该算法的基本思想是:对每一个要排序的元素统计小于它的所有元素的个数,从而得到该元素在整个序列中的位置。其时间复杂度为o(n^2)。枚举排序串行算法伪代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 输入为:a[1], a[2] , ... , a[n] 2 3 输出为:b[1], b[2] , ..., b[n] 4 5 for i =1 to n do 6 7 1)k =1 8 9 2)for j = 1 to n do 10 11 if a[i] > a[j] then 12 13 k= k + 1 14 15 endif 16 17 endfor 18 19 3)b[k] = a[i] 20 21 endfor
通过将此串行化算法改造为并行算法,得到其并行算法的伪代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 输入无序数组为:a[1], a[2] , ... , a[n] 2 输出有序数组为:b[1], b[2] , ..., b[n] 3 Begin 4 P0播送a[1], a[2] , ... , a[n]给所有Pi 5 for all Pi where 1=<i<=n para do 6 k =1 7 for j = 1 to n do 8 if (a[i] > a[j]) or (a[i]=a[j] and i>j) then 9 k= k + 1 10 endif 11 endfor 12 endfor 13 P0收集k并按序定位 14 End
通过将串行枚举排序算法改造为并行枚举排序算法,我们可以看到排序N个元素需要N个处理器,每个处理器(节点或进程)的时间复杂度为O(N),总的计算复杂度为O(N),但是在并行算法中,通信复杂度占据了很重要的位置,在此算法中,总的通信复杂度为O(N^2)。好了,相信你对枚举排序也有了大致的了解了,下面我将用Erlang实现并行枚举排序,由于环境所限,主要是在单机上的多个进程间来进行枚举排序。
Erlang代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 %% 启动排序时,需存储各节点信息:使用store(Key,Value)方法 2 %%然后,便可启动enum_sort(Data)进行排序 3 -module(enum_sort). 4 -export([start/2,enum_sort/1,store/2,lookup/1]). 5 6 start(Node,Key) -> 7 Pid = spawn(Node, fun() -> loop(Key) end). 8 9 enum_sort(Data) -> 10 register(server,self()), 11 store(server,node()), 12 rpc(Data), 13 Final = merge(Data,[]), 14 io:format("~nEnum Sorting success: ~p~n",[lists:reverse(Final)]). 15 16 rpc(Data) -> 17 Len = length(Data) -1, 18 send(Len,Data), 19 Loc = enum_once(Data,Len+1), 20 self() ! {self(),Loc}. 21 22 store(Key,Value) -> 23 put(Key,Value). 24 25 lookup(Key) -> 26 get(Key). 27 28 %% 向N-1个进程发送排序数据 29 send(1 , Data) -> 30 Node1 = lookup(1), 31 Pid = start(Node1,1), 32 Pid ! {node(), self(), Data}; 33 send(N,Data) -> 34 NodeN = lookup(N), 35 Pid = start(NodeN,N), 36 Pid ! {node(), self(), Data}, 37 send(N-1,Data). 38 39 genNode(N) -> 40 Temp = lists:concat([node,N,"@dell-PC"]), 41 Ret = list_to_atom(Temp). 42 43 genAtom(N) -> 44 Temp = lists:concat([process,N]), 45 Ret = list_to_atom(Temp). 46 47 %%节点N准备接受排序数据 48 loop(N) -> 49 %io:format("1---Current node:~p~n",[node()]), 50 receive 51 {Node,Pid,die} -> 52 disconnect_node(Node), 53 io:format("Node (~p) disconnected~n",[Node]), 54 quit; 55 {Node,Pid,L} -> 56 store(server,Node), 57 Loc = enum_once(L,N), 58 %io:format("Come from Server Pid=~p, Node = ~p~n",[Pid,Node]), 59 %io:format("Process Node Pid = ~p Location=~p~n",[self(),Loc]), 60 {server,Node} ! {node(), genAtom(N),Loc}, 61 loop(N) 62 end. 63 64 %% Pi进程运行enum_once一次,得到一个K值 65 enum_once(L,I) -> 66 if 67 I =:= length(L) -> 68 Node = lookup(server); 69 I =/= length(L) -> 70 Node = lookup(I) 71 end, 72 %io:format("Node (~p) is processing one enum~n",[node()]), 73 Loc = lookup(L,I,1), 74 Ele = lists:nth(I,L), 75 io:format("Node(~p) Data[~p]= ~p Sorting loc=( ~p)~n",[node(),I,Ele,Loc]), 76 Loc. 77 78 79 %% 计算Pi进程,即第I个元素在有序列表中的位置 80 lookup(L,I,J) -> 81 if 82 J > length(L) -> 83 0; 84 J =< length(L) -> 85 Ai = lists:nth(I,L), 86 Aj = lists:nth(J,L), 87 Value1 = Ai > Aj , 88 Value2 = (Ai =:= Aj) and ( I > J ), 89 Value = Value1 or Value2, 90 if 91 Value =:= true -> 92 1 + lookup(L,I,J+1); 93 Value =:= false -> 94 lookup( L, I, J+1) 95 end 96 end. 97 98 99 %% 总控端将收集到的K值进行整理合并 100 merge(Data,Ret) -> 101 Len = length(Data), 102 Cur = length(Ret), 103 if Cur < Len -> 104 receive 105 {Pid, Loc} -> 106 Pn = length(Data), 107 Ret1 = [{ Pn, Loc } | Ret], 108 merge(Data,Ret1); 109 {Node, Pid,Loc} -> 110 Process = atom_to_list(Pid), 111 ListAfter = lists:nthtail( length("process"), Process), 112 Pi = list_to_integer(ListAfter), 113 Ret1 = [{ Pi ,Loc } | Ret], 114 merge(Data,Ret1) 115 end; 116 Cur =:= Len -> 117 %% io:format("{Key, Value} = {element locations, sorting location} = (~p)~n",[Ret]), 118 sort(Data,Ret, []) 119 end. 120 121 %% 将收集的K值,进行排序处理 122 sort( Data, [ ] ,Temp) -> Temp; 123 sort( Data, L, Temp ) -> 124 H = lists:nth(1,L), 125 { X, Y} = H, 126 Element = lists:nth( X, Data), 127 %io:format("Element = (~p) X = (~p) Y = (~p)~n",[Element,X,Y]), 128 Len = length(Temp), 129 if Y =:= Len -> 130 Next = [ Element | Temp], 131 Filter = lists:delete( H ,L), 132 sort(Data, Filter, Next); 133 Y =/= Len -> 134 After = lists:delete(H, L), 135 %%Filter = lists:concat( After, [H]), 136 Filter = lists:append(After, [H]), 137 %% io:format("H = (~p) After = (~p) Filter =(~p)~n",[H,After,Filter]), 138 sort( Data, Filter, Temp) 139 end.
下面看一下程序运行结果:
好了,我主要将枚举排序的主要过程讲了,还有Erlang实现的源码放在这了,这是以前写的,有些方面写的可能有些复杂,具体细节就不说了,如有问题,可以留言给我。我将在以后的文章中写两个关于并行快排和PSRS的排序,欢迎大家继续支持及提出宝贵意见!
注:如无特殊说明,本博客内容纯属原创,转载请注明:http://www.cnblogs.com/itfreer/ | IT闲人,谢谢!