• erlang进程的调度效率


    一、概述

        与大多数的进程相反,Erlang中的并发很廉价,派生出一个进程就跟面向对象的语言中分配一个对象的开销差不多。 在启动一个复杂的运算时,启动运算、派生进程以及返回结果后,所有进程神奇的烟消云散,它们的内存、邮箱、所持有的数据库句柄、它们打开的套接字,以及一些不乐意手工清理的东西,都一并消失。

        Erlang进程不是操作系统进程,它们由erlang运行时系统实现,比线程要轻量的多,单个erlang系统可以轻易地派生出成百上千个进程。运行时系统中所有的进程都是隔离的,单个进程的内存不与其他进程共享,也不会被濒死或跑疯的进程破坏。

        在操作系统中,典型的线程会在地址空间中为自己预留数兆的栈空间(也就是说32位的机器上并发线程最多也就几千个),栈空间溢出便会导致崩溃。erlang进程在启动时栈空间只有几百个字节,并会按需伸缩。

    二、示例

     1 14> Pid = spawn(fun() -> timer:sleep(60000), primes:primelist(100000) end).    %% 派生一个进程,等待1分钟后做素数运算。 素数功能代码已提前编写好。
     2 <0.55.0>
     3 15> erlang:process_info(Pid).
     4 [{current_function,{timer,sleep,1}},
     5  {initial_call,{erlang,apply,2}},
     6  {status,waiting},
     7  {message_queue_len,0},
     8  {messages,[]},
     9  {links,[]},
    10  {dictionary,[]},
    11  {trap_exit,false},
    12  {error_handler,error_handler},
    13  {priority,normal},
    14  {group_leader,<0.26.0>},
    15  {total_heap_size,233},
    16  {heap_size,233},       %%刚启动的erlang进程所占的堆的大小仅为233字节,栈的大小10个字节。
    17  {stack_size,10},
    18  {reductions,43},       %%创建erlang进程仅消耗了43个reductions, 可见erlang的轻量。 
    19  {garbage_collection,[{min_bin_vheap_size,46422},
    20                       {min_heap_size,233},
    21                       {fullsweep_after,65535},
    22                       {minor_gcs,0}]},
    23  {suspending,[]}]
    24 17> erlang:statistics(run_queue).     %% 进程处于挂起状态,堆、栈、时间片消耗都不会变
    25 0
    26 22> erlang:statistics(run_queue).     %% 进程进入运行队列,准备被调度。
    27 1
    28 23> erlang:process_info(Pid).
    29 [{current_function,{primes,'-primelist/3-lc$^0/1-0-',2}},
    30  {initial_call,{erlang,apply,2}},
    31  {status,runnable},
    32  {message_queue_len,0},
    33  {messages,[]},
    34  {links,[]},
    35  {dictionary,[]},
    36  {trap_exit,false},
    37  {error_handler,error_handler},
    38  {priority,normal},
    39  {group_leader,<0.26.0>},
    40  {total_heap_size,393300},
    41  {heap_size,75113},      %%进程在做素数计算, 堆栈大小随着需要开始增加。 消耗的时间片也会随着计算量增加而增加。
    42  {stack_size,13773},
    43  {reductions,89874499},
    44  {garbage_collection,[{min_bin_vheap_size,46422},
    45                       {min_heap_size,233},
    46                       {fullsweep_after,65535},
    47                       {minor_gcs,3085}]},
    48  {suspending,[]}]
    49 27> erlang:statistics(run_queue).
    50 0
    51 29> erlang:process_info(Pid).
    52 [{current_function,{primes,'-primelist/3-lc$^0/1-0-',2}},
    53  {initial_call,{erlang,apply,2}},
    54  {status,runnable},
    55  {message_queue_len,0},
    56  {messages,[]},
    57  {links,[]},
    58  {dictionary,[]},
    59  {trap_exit,false},
    60  {error_handler,error_handler},
    61  {priority,normal},
    62  {group_leader,<0.26.0>},
    63  {total_heap_size,393300},
    64  {heap_size,75113},
    65  {stack_size,87},     %% 随着计算的结束, 栈空间开始收缩, 这里可以看到堆空间没有变,堆的分配是由erlang GC来控制的, 进程结束时由GC来回收。 
    66  {reductions,958602600},
    67  {garbage_collection,[{min_bin_vheap_size,46422},
    68                       {min_heap_size,233},
    69                       {fullsweep_after,65535},
    70                       {minor_gcs,33117}]},
    71  {suspending,[]}]

    三、进程的调度

        由于Erlang虚拟机对SMP的支持,每个操作系统线程都可以运行在一个调度器上,每个调度器拥有一自己的运行队列,这样避免了多个调度器同时调度在运行队列中的任务产生的冲突,但是如何保证调度队列任务分配的公平性,Erlang引入了一个高效和公平的概念,迁移逻辑。迁移逻辑利用在系统中收集的统计数据,控制和平衡了队列。

        Erlang启动模拟器的时候可以加上+S去指定最大调度器数和可用调度器数。可用调度器数可以在模拟器运行时更改。

     -> erl +S 16:8
    Erlang R16B03-1 (erts-5.10.4) [source] [64-bit] [smp:16:8] [async-threads:10] [hipe] [kernel-poll:false]
    
    Eshell V5.10.4  (abort with ^G)
    1> erlang:system_info(schedulers_online).
    8
    2> erlang:system_info(schedulers).
    16

    下面我们尝试起多个erlang进程去做素数运算,然后看看调度队列情况。

     -> erl +S 8:8     %%启动8个调度器同时可用。
    Erlang R16B03-1 (erts-5.10.4) [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false]
    
    Eshell V5.10.4  (abort with ^G)
    1>  erlang:statistics(run_queue).        %%之前没有任务进入调度队列
    0
    2> [spawn(fun() -> timer:sleep(5000), primes:primelist(10000) end) || _ <- lists:seq(1, 10)].           %% 同时启动10个进程做素数运算
    [<0.36.0>,<0.37.0>,<0.38.0>,<0.39.0>,<0.40.0>,<0.41.0>,
     <0.42.0>,<0.43.0>,<0.44.0>,<0.45.0>]
    5>  erlang:statistics(run_queue).            %%同时有7个进程被调度运行,3个进程出去准备状态
    3
    7>  erlang:statistics(run_queue).            %%两个任务被换入。
    1
    8>  erlang:statistics(run_queue).            %%任务执行完毕,没有任务处于等待状态。
    0
    

      那么问题来了,在同样的硬件环境下,是不是调度器越多,进程处理的速度越快呢?列出测试结果:

     -> erl +S 4:4     %%%启动4个可用调度器
    Erlang R16B03-1 (erts-5.10.4) [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
    2> [spawn(fun() -> {P1, _P2} = timer:tc(primes, primelist, [100000]), io:format("timer:~p~n", [P1]) end) || _ <- lists:seq(1, 10)].
    timer:185078651
    timer:190735178
    timer:192956743
    timer:193186850
    timer:220074562
    timer:222929652
    timer:234756209
    timer:235304593
    timer:235474721
    timer:236500425
    
     -> erl +S 8:8   %%%启动8个可用调度器
    Erlang R16B03-1 (erts-5.10.4) [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false]
    
    Eshell V5.10.4  (abort with ^G)
    1>
    1>
    1>
    1> [spawn(fun() -> {P1, _P2} = timer:tc(primes, primelist, [100000]), io:format("timer:~p~n", [P1]) end) || _ <- lists:seq(1, 10)].
    [<0.35.0>,<0.36.0>,<0.37.0>,<0.38.0>,<0.39.0>,<0.40.0>,
     <0.41.0>,<0.42.0>,<0.43.0>,<0.44.0>]
    timer:187405676
    timer:187568120
    timer:188255698
    timer:188577806
    timer:190819642
    timer:191208176
    timer:235470698
    timer:236842370
    timer:237630863
    timer:238206383
    
     -> erl +S 16:11
    Erlang R16B03-1 (erts-5.10.4) [source] [64-bit] [smp:16:11] [async-threads:10] [hipe] [kernel-poll:false]
    
    Eshell V5.10.4  (abort with ^G)
    1> [spawn(fun() -> {P1, _P2} = timer:tc(primes, primelist, [100000]), io:format("timer:~p~n", [P1]) end) || _ <- lists:seq(1, 10)].
    [<0.35.0>,<0.36.0>,<0.37.0>,<0.38.0>,<0.39.0>,<0.40.0>,
     <0.41.0>,<0.42.0>,<0.43.0>,<0.44.0>]
    
    timer:243000833
    timer:243636514
    timer:244753411
    timer:245005027
    timer:245296405
    timer:245356679
    timer:245659526
    timer:245662159
    timer:245731926
    timer:245779971
    

      从测试结果看,并不是调度器越多越好,也不是越少越好,合适实际应用场景才是最好的。

  • 相关阅读:
    Marker
    Log4j 2
    Spring 中 CharacterEncodingFilter 失效?
    Java 泛型通配符
    待修 Bug
    Java Class SecurityManager
    bzoj 5210: 最大连通子块和【动态dp+树剖+线段树+堆】
    洛谷 P4719 【模板】动态dp【动态dp】
    bzoj 2555: SubString【后缀自动机+LCT】
    洛谷 P4125 [WC2012]记忆中的水杉树【扫描线+set+线段树】
  • 原文地址:https://www.cnblogs.com/liquan2005/p/8870011.html
Copyright © 2020-2023  润新知