Erlang/Elixir Erlang 源码阅读 -- Number of Active Schedulers

yfractal · December 09, 2018 · 7059 hits

Erlang 使用 scheduler 来对 Erlang process 进行调度。

为了可以利用多核资源,在启动的时候,会根据 CPU 的核心数或物理线程数创建 schduler,每个 schduler 都在一个线程中运行,而每一个线程被绑定到了对应的 CPU core。

Erlang runtime 会根据负载的情况,动态计算所需要的 scheduler 的数量,如果负载过高,则增加 scheduler 数量,如负载过低,则减少 scheduler 的数量。

如果某个 scheduler 负载过高,也会把这个 scheduler 的 process 迁移(migrate)给别的 scheduler。

如果一个 scheduler 没有 process 可以执行,就会从其他 scheduler 里偷 process

利用多核资源还有其他方案,在 Erlang 没有支持 SMP 之前(1998 年由一篇硕士论文提出,2006 官方正式发布),可以通过启动多个 Erlang VM 来达到类似的效果,但显然没有多线程、共享内存灵活和高效。

再比如 Rust 的 tokio,则是使用线程池来执行 callback,同样可以利用多核,同时还解决了公平调度的问题,但这样就需要锁来避免多个线程同时操作同一个变量, 好在 Rust 解决起这个问题并不难。

下面主要是介绍,在 check_balance 的时候,scheduler 的数量是如何计算的。

Check Balance

Trigger Condition

Erlang VM 没执行一定的 reductions 的时候,就会触发 check_balance 方法,并且只有一个 scheduler 会触发这个方法,具体可以看上一篇文章

Disable Scheduler Compaction of Load

如果不需要动态计算 scheduler 的话,可以通过设置 +scl true 这个参数,来关掉这个公能。毕竟有些场景节能不是考虑的问题。

Formula

首先 active 必须要落在一个区间内,active 最小不能小于 max(1, half_full_scheds),最大不能大于 blnc_no_rqs。

half_full_scheds 是指,在一半的时间里,没有进入等待状态(ERTS_RUNQ_FLG_OUT_OF_WORK)的 scheduler 的数量。

blnc_no_rqs 通过 get_no_runqs 获得,是指这一阶段内,使用过的 scheduler 数量。

之后,如果上一次活跃的数量,小于当前活跃的数量,则将其设置为当前活跃的数量

如果不满足这个条件,则

full_scheds 是指在这个阶段内,从来没有进入等待状态的 scheduler 的数量。

既在这个阶段内,有 scheduler 没有进入等待状态,则将 scheduler 的数量设置为所有 scheduler 执行的 reductions 数量除以触发 balance_check 需要的 reductions 的数量。

否则将 scheduler 设置为 上一次的活跃数减 1。

最后,还会根据负载情况,做一次修正。

prev_rise 是上一次,scheduler 增加的时候的数量,设置方法是:

if (balance_info.last_active_runqs < current_active)
   ERTS_BLNCE_SAVE_RISE(current_active, mmax_len, scheds_reds);

也就是说,如果负载减少的不是很多的话,scheduler 的数量会保持之前的数量不变。

源代码如下

    if (!forced && half_full_scheds != blnc_no_rqs) {
        int min = 1;
        if (min < half_full_scheds)
            min = half_full_scheds;

        if (full_scheds) {
            active = (scheds_reds - 1)/ERTS_RUNQ_CHECK_BALANCE_REDS_PER_SCHED+1;
        }
        else {
            active = balance_info.last_active_runqs - 1;
        }

        if (balance_info.last_active_runqs < current_active) {
            ERTS_BLNCE_SAVE_RISE(current_active, mmax_len, scheds_reds);
            active = current_active;
        }
        else if (active < balance_info.prev_rise.active_runqs) {
            if (ERTS_PERCENT(mmax_len,
                             balance_info.prev_rise.max_len) >= 90
                && ERTS_PERCENT(scheds_reds,
                                balance_info.prev_rise.reds) >= 90) {
                active = balance_info.prev_rise.active_runqs;
.active_runqs;
            }
        }

        if (active < min)
            active = min;
        else if (active > blnc_no_rqs)
            active = blnc_no_rqs;

        if (active == blnc_no_rqs)
            goto all_active;
    }
No Reply at the moment.
You need to Sign in before reply, if you don't have an account, please Sign up first.