BEAM & ERTS
erlang
#BEAM是什么? BEAM全称是Bogdan/Björn’s Erlang Abstract Machine
#ERTS是什么? ETRS全称是Erlang Run-Time System
#erlang的进程是什么? erlang的进程实际上是虚拟的概念。我们通常再说某个进程的运行实际上是由一个叫scheduler的线程去执行这个进程的函数接收消息等等。erlang的进程是由栈,私有堆,PCB(process control block)。进程堆和栈共同占用一块连续的内存空间,堆空间由低地址向高地址增长,栈空间由高地址向低地址增长,当堆顶和栈顶一样时,可以判定堆栈空间已满。
#scheduler是什么? scheduler是整个虚拟机实际工作的线程,他有4个队列,分别为’max’, ‘high’, ‘normal’, ‘low’,这个4个队列里放的就是erlang的进程,队列里的erlang进程按顺序被scheduler运行。这4个队列是有优先级的,字面上的意思max>high>normal=low,当高优先级的队列中有进程的时候,优先运行高优先级队列中的erlang进程。
#smp是什么? smp的目的是充分利用当前计算机的多核特性,给每个核绑定一个scheduler,这样达到并行处理的目的,提高了beam的处理能力。现在的beam都是默认启动smp的,所以默认就有多个scheduler,否则就只有一个scheduler线程。在SMP硬件结构中,每个CPU对应一个自己的缓存,所有CPU都共享整个计算机的内存。缓存的基本单位是缓存线Cacheline,缓存线的长度是固定的,所以每次缓存从内存读取数据都是连续的,例如缓存线的长度是16位,那么如果CPU_1要缓存一个长度为4位的数据,那么就要从内存中提取包含这4位数据的连续的16位的数据到缓存中,如果这16位的数据中有6位数据是CPU_2的缓存也存在的,当CPU_2更新了这6位数据,它就要把新的数据更新会内存。CPU_1通过监控总线了解到CPU_2对自己的缓存的更改,接着也更新自己的缓存。
#公平调度和抢占式调度是什么? 每个erlang进程都有个reduction counter(进程创建的时候赋值2000),当scheduler执行完一次完整的erlang函数调用后,该erlang进程的reduction counter减一,当reduction counter归零,这个erlang进程重新会本队列的尾部,scheduler运行队列中下一个erlang进程,这样才能保证每个erlang的进程都能有机会被scheduler执行,而不会因为某个erlang进程一直在运行而阻塞整个scheduler的执行队列,这就是抢占式调度或者公平调度。
#scheduler的任务迁移是什么? 在smp模式下,多个scheduler有多个erlang进程的执行队列,BEAM会对所有的队列做统计求出个队列长度平均值,并根据现有的情况作出一个迁移计划来,把队列长度高于平均值的scheduler的队列里的待执行erlang进程迁移到队列小于平均值的scheduler的队列中,最终维护大体平均的执行队列。
#什么是async threads? async threads实际上是处理IO的一些事。由于IO基本上都是时间特别长的任务,所以单独的线程来处理,否则放在scheduler中严重阻塞ERTS的运行。
#什么是allocator? allocator是ERTS自己的内存分配器,每个调度器线程都有自己的一整套allocator,包括
- temp_alloc
用于分配临时数据的内存分配器 - eheap_alloc
用于分配Erlang堆数据的内存分配器,例如Erlang进程的独占堆。 - binary_alloc
用于分配Erlang二进制的内存分配器。 - ets_alloc
用于分配ETS数据的内存分配器。 - driver_alloc
用于分配驱动(driver)的内存分配器。 - sl_alloc
用于分配短生存周期的内存区块(memory blocks)的内存分配器。 - ll_alloc
用于分配长生存周期的内存区块的内存分配器,例如Erlang的代码。 - fix_alloc
用于分配被频繁调用的固定长度的数据类型的内存分配器。 - std_alloc
用于分配大多数没被其他类型的内存分配器分配的数据的内存分配器。 - sys_alloc
用于通过malloc实现从OS里获取内存的内存分配器。 - mseg_alloc
一个内存片段分配器。mseg_alloc被用来给其他的Erlang分配器分配内存片段,并且仅仅在操作系统支持mmap系统调用的时候才能有效使用。这些分配给其他内存分配器的内存片段在被释放的时候不会立即销毁并还给操作系统,而是作为片段缓存一段时间。当其他内存分配器调用mseg_alloc来获取内存片段的时候,优先使用缓存的内存片段,而不是向操作系统创建新的内存片段。这样做是为了尽量少的做操作系统调用。
之所以要定义这么多不同的allocator是为了让不同的数绝结构分别放在不同的内存区域中,期望有效的减少内存碎片的产生。
#什么是carrier? ERTS的内存分配器(allocator)管理的内存区域中,内存区块被放置的空间叫做载体(carrier)。一个载体只被放置在独立的内存片段(由mseg_alloc分配)或堆片段(由sys_alloc分配)中。多块载体(Multiblock carrier)用来储存多个内存区块(block)。单块载体(Singleblock carrier)用来存储单个内存区块。内存区块的大小大于sbct(singleblock carrier threshold)参数的被存储在单块载体中,小于sbct参数的存储在多块载体中。通常任何一个内存分配器都会创建一个主多块载体(main multiblock carrier),主多块载体永远不会被释放掉,它的大小由mmbcs(main multiblock carrier size)参数来决定。
largest multiblock carrier size(lmbcs):多块载体最大值
smallest multiblock carrier size(smbcs):多块载体最小值
multiblock carrier growth stages(mbcgs):多块载体增长阶段
一个内存分配器通过mseg_alloc分配的多块载体大小,由最近的多块载体大小nc和上述的几个值来决定:
当nc <= mbcgs时:smbcs+nc*(lmbcs-smbcs)/mbcgs
当nc > mbcgs时:lmbcs
通过sys_alloc分配的多块载体大小,由ycs(sys_alloc carrier size )参数来决定。
#内存分配策略(memory allocation strategy)? 空闲的内存区块都是马上被合并的,但是空闲区块位于被使用的区块中间的话会造成时间复杂度的提升。内存的分配策略是找到这些空闲的区块的方法。究竟采用那种策略由as(allocation strategy)参数决定。
- Best fit
策略:找到满足所需大小的区块中最小的区块。
实现:平衡二叉查找树,时间复杂度是log N,N是空闲区块的大小。 - Address order best fit
策略:找到满足所需大小的区块中最小的区块。如果找到多个区块,取内存地址最小的那个。
实现:平衡二叉查找树,时间复杂度是log N,N是空闲区块的大小。 - Address order first fit
策略:找到满足所需大小的区块中内存地址最小的区块。
实现:平衡二叉查找树,时间复杂度是log N,N是空闲区块的大小。 - Address order first fit carrier best fit
策略:找到满足所需大小的区块的载体中内存地址最小的载体,并根据Best fit策略取得其中的区块。
实现:平衡二叉查找树,时间复杂度是log N,N是空闲区块的大小。 - Address order first fit carrier address order best fit
策略:找到满足所需大小的区块的载体中内存地址最小的载体,并根据Adress order best fit策略取得其中的区块。
实现:平衡二叉查找树,时间复杂度是log N,N是空闲区块的大小。 - Good fit
策略:试图去找到Best fit,但是尽量使搜索路径最小。
实现:好复杂,不翻译了。 - A fit
策略:不去查找是否有满足的区块,只取一个空闲的区块并看其是否满足要求,这个策略只打算用于临时内存分配。
实现:去free-list查找第一个空闲的区块,如果满足就用,不满足就创建新的载体。时间复杂度是常量。
这个策略在ERTS版本5.6.1之后除了temp_alloc以外其他的内存分配器拒绝使用。
除了上述的内存分配器外还有一些预分配内存分配器用来给特定的数据类型分配内存。这些预分配内存分配器会在ERTS启动的时候预先给某种数据类型分配固定大小的内存。当有空闲的预分配内存的时候,优先使用,否则由常规内存分配器分配内存。这些预分配内存分配器速度比常规的内存分配器快很多,但是只能满足有限的需求。