• [Erlang0009][OTP] 高效指南 进程


    原文链接:http://www.erlang.org/doc/efficiency_guide/processes.html

    错误之处欢迎指正。

    8 进程

     

    8.1 创建Erlang进程

     

    相比操作系统的线程和进程来说,Erlang的进程更为轻量。

     

    一个新创建的进程在non-SMP、不支持HiPE的虚拟机上占用内存309words。(支持SMP和HiPE的话,内存占用会翻倍。)这个数字可以这样来得到:

    Erlang (BEAM) emulator version 5.6 [async-threads:0] [kernel-poll:false]
    
    Eshell V5.6  (abort with ^G)
    1> Fun = fun() -> receive after infinity -> ok end end.
    #Fun<...>
    2> {_,Bytes} = process_info(spawn(Fun), memory).
    {memory,1232}
    3> Bytes div erlang:system_info(wordsize).
    309

    这里包括233words的堆空间(也包括栈)。垃圾回收器会按需增加堆的大小。


    进程的主循环必须是尾递归。否则,在进程退出之前,栈空间会不同的增长。

    loop() -> 
      receive
         {sys, Msg} ->
             handle_sys_msg(Msg),
             loop();
         {From, Msg} ->
              Reply = handle_msg(Msg),
              From ! Reply,
              loop()
      end,
      io:format("Message is processed~n", []).

    io:format/2永远不会被调用,但是在递归执行loop/0的过程中,它的地址每次都会推到栈里去。正确的尾递归版本的函数应该是这样的:

    DO
    
       loop() -> 
          receive
             {sys, Msg} ->
                handle_sys_msg(Msg),
                loop();
             {From, Msg} ->
                Reply = handle_msg(Msg),
                From ! Reply,
                loop()
        end.

    初始的堆大小

    默认的233words的初始堆空间在一个有成百上千乃至上万的Erlang系统中是非常保守的。垃圾回收器会按需调整堆空间的大小。

     

    在一个较少进程组成的系统中,用erl的+h选项或者在每个进程启动的时候使用spawn_opt/4的min_heap_size选项都可以增加最小堆的大小以提高性能。

     

    这里也存在两面性的问题:首先,虽然垃圾回收器可以增大堆空间,但这个过程是一步一步增加的,这比在进程被创建时直接创建一个较大的堆空间要代价更高。但是,如果进程的堆空间比其存储的数据多出很多,垃圾回收器也会回收空间;设置最小堆空间来避免这个问题。

     

    注意:虚拟机可能会用更大的内存。而且因为垃圾回收器不经常光顾,大的二进制数据可能会存留更长的时间。

     

    在一个很多进程组成的系统里,时间很短的计算工作可以指派给一个新的进程,分配的最少堆空间可以多一些。当进工作完成时,把计算结果发送给另一个进程然后退出。如果最小堆空间能够被准确计算,那么进程就不比做任何垃圾回收操作。但在没有经过适当评测时这类优化不应被使用。

     

    8.2 进程消息

     

    进程之间的所有消息数据的传递都是拷贝,除了同一个节点的refc binary(见《二进制的构造和匹配(1)》)。

     

    当消息发送到另一个节点的进程时,会先编码成Erlang External Format,然后通过TCP/IP套接字来发送。接受的节点会解码消息,并发送到相应的进程。

     

    常量池

     

    Erlang terms常量现在被保存在常量池里;每一个被加载的模块都有自己的池。下面的函数:

    DO (in R12B and later)
    
    days_in_month(M) ->
        element(M, {31,28,31,30,31,30,31,31,30,31,30,31}).

    不会在每次调用的时候都创建元组,(只有在下次垃圾回收之后才会重新创建),元组会一直保存在模块的常量池里。

     

    但是如果常量被发送给另一个进程(或者储存在ETS表中),它就会被拷贝。原因是,运行时系统必须能够持续追踪所有常量的引用,以便能够正确卸载包含常量的代码。(当代码被卸载时,常量会被拷贝到引用它们的进程的堆上。)常量的拷贝或许会在以后的发布版本中被淘汰。

     

    被抛弃的共享

     

    共享的子项将不再被保存,当一个项被发送给另一个进程,或在调用spawn创建进程时作为初始化参数被传递,或是存储在ETS表中。这是一个优化。大部分应用不要带着共享子项发送消息。

     

    下面是共享子项被创建的例子:

    kilo_byte() ->
        kilo_byte(10, [42]).
    
    kilo_byte(0, Acc) ->
        Acc;
    kilo_byte(N, Acc) ->
        kilo_byte(N-1, [Acc|Acc]).

    kilo_byte/0创建了一个深列表。如果我们调用list_to_binary/1,我们可以将这个列表转换成1024字节的二进制:

    1> byte_size(list_to_binary(efficiency_guide:kilo_byte())).
    1024

    用erts_debug:size/1 BIF,我们可以看到这个列表占有22words的堆空间:

    2> erts_debug:size(efficiency_guide:kilo_byte()).
    22

    用erts_debug:flat_size/1 BIF,我们能够计算出如果共享部分被忽略的话这个列表的大小。这也是当它被发送给其他进程或存在ETS中的大小:

    3> erts_debug:flat_size(efficiency_guide:kilo_byte()).
    4094

    我们可以核实共享部分会被丢弃,如果把数据插入ETS表:

    4> T = ets:new(tab, []).
    17
    5> ets:insert(T, {key,efficiency_guide:kilo_byte()}).
    true
    6> erts_debug:size(element(2, hd(ets:lookup(T, key)))).
    4094
    7> erts_debug:flat_size(element(2, hd(ets:lookup(T, key)))).
    4094

    当数据通过ETS表来传递,erts_debug:size/1和erts_debug:flat_size/1返回相同的大小,都不包括共享的部分。

    在未来的Erlang/OTP发布版本中,我们可能会实现保存共享部分的方法。我们不打算把保存共享作为默认的功能,因为那将对绝大多数的Erlang应用不利。

     

    8.3 SMP虚拟机

     

    SMP虚拟机会给运行了多个Erlang调度线程(与核的个数相同)的多核或多CPU计算机带来好处。每个调度线程都会像未开SMP的虚拟机调度线程一样来调度进程。

     

    想要通过SMP虚拟机在性能上获益,你的应用必须在绝大多数情况下跑在多个进程上。否则Erlang虚拟机还是只能在同一时间运行一个Erlang进程。但同时你必须承担锁的额外代价。尽管我们极力减少锁的代价,但它不可能降为零。

     

    那些看起来是并发的基准经常都是顺序的。例如estone基准,它是完全顺序的。就是最普通的环基准的实现;通常当一个进程被激活时,其他进程在receive状态中等待。

     

    percept应用可被用来测试你的应用,看是否有并发的潜力

  • 相关阅读:
    Ural 1201 Which Day Is It? 题解
    Ural 1250 Sea Burial 题解
    2019 Multi-University Training Contest 2: 1010 Just Skip The Problem 自闭记
    Codeforces 718A Efim and Strange Grade 程序分析
    CentOS7 修改MySql默认端口
    Ubuntu 18.04 版本中安装mysql 8的方法
    NET_NET深入体验与实战 第一章 .NET你知道 1.1什么是 .NET
    第五课
    c# 第一节课 一些简单的应用
    MDI窗体和窗体之间的操作总结
  • 原文地址:https://www.cnblogs.com/liangjingyang/p/2708546.html
Copyright © 2020-2023  润新知