- 浏览: 87408 次
文章分类
最新评论
分析erlang热更新实现机制
Joe Armstrong在描述Erlang的设计要求时,就提到了软件维护应该能在不停止系统的情况下进行。在实践中,我们也因为这种不停止服务的热更新获益良多。那么Erlang是如何做到热更新的呢?这就是本文要讨论的问题。
在前面的文章也说到了。erlang VM为每个模块最多保存2份代码,当前版本'current'和旧版本'old',当模块第一次被加载时,代码就是'current'版本。如果有新的代码被加载,'current'版本代码就变成了'old'版本,新的代码就成了'current'版本。erlang用两个版本共存的方法来保证任何时候总有一个版本可用,对外服务就不会停止。
前言
为什么代码热更新时不影响进程运行?
为什么进程要使用外部调用(M:F/A)才能切换到新代码?
为什么可以同时使用2个版本的代码?
为什么只能一个模块一个模块热更?
....
我们总会有很多疑问,但一切的答案都在源码上。现在深入剖析下erlang热更新实现机制,相信你的疑惑可以找到答案。
源码剖析
以下是erlang热更新的三个过程:
c(Mod) -> compile:file(Mod), %% 编译erl成beam文件 code:purge(Mod), %% 清理模块(同时杀掉运行'old'代码的进程,'current'的不受影响) code:load_file(Mod). %% 加载beam代码到vm
热更新加载beam代码到vm,这一步是调用了 erlang:load_module() 实现,文章重点说下这个函数。(以R16B02作说明)
%% erlang:load_module/2 load_module(Mod, Code) -> case erlang:prepare_loading(Mod, Code) of {error,_}=Error -> Error; Bin when erlang:is_binary(Bin) -> case erlang:finish_loading([Bin]) of ok -> {module,Mod}; {Error,[Mod]} -> {error,Error} end end.
以上主要是2个过程:
1、 erlang:prepare_loading() 预加载beam的操作,是一个解析beam的过程
2、erlang:finish_loading() 实现代码加载到vm的过程
预加载beam
现在看下erlang:prepare_loading() ,这是个bif函数,实现预加载beam:
/* * beam_bif_load.c prepare_loading_2函数,实现 erlang:prepare_loading() */ BIF_RETTYPE prepare_loading_2(BIF_ALIST_2) { byte* temp_alloc = NULL; byte* code; Uint sz; Binary* magic; Eterm reason; Eterm* hp; Eterm res; if (is_not_atom(BIF_ARG_1)) { error: erts_free_aligned_binary_bytes(temp_alloc); BIF_ERROR(BIF_P, BADARG); } // 复制原始的beam文件数据 if ((code = erts_get_aligned_binary_bytes(BIF_ARG_2, &temp_alloc)) == NULL) { goto error; } magic = erts_alloc_loader_state(); sz = binary_size(BIF_ARG_2); // 预加载beam(解析beam,加载数据,生成导出函数) reason = erts_prepare_loading(magic, BIF_P, BIF_P->group_leader, &BIF_ARG_1, code, sz); // 释放beam数据空间 erts_free_aligned_binary_bytes(temp_alloc); if (reason != NIL) { hp = HAlloc(BIF_P, 3); res = TUPLE2(hp, am_error, reason); BIF_RET(res); } hp = HAlloc(BIF_P, PROC_BIN_SIZE); res = erts_mk_magic_binary_term(&hp, &MSO(BIF_P), magic); erts_refc_dec(&magic->refc, 1); BIF_RET(res); }下面是解析beam的过程:
/* * beam_load.c erts_prepare_loading函数,实现beam解析,加载数据,生成导出函数 */ Eterm erts_prepare_loading(Binary* magic, Process *c_p, Eterm group_leader, Eterm* modp, byte* code, Uint unloaded_size) { Eterm retval = am_badfile; LoaderState* stp; stp = ERTS_MAGIC_BIN_DATA(magic); stp->module = *modp; stp->group_leader = group_leader; #if defined(LOAD_MEMORY_HARD_DEBUG) && defined(DEBUG) erts_fprintf(stderr,"Loading a module\n"); #endif /* * Scan the IFF file. */ CHKALLOC(); CHKBLK(ERTS_ALC_T_CODE,stp->code); // 检查beam文件格式,生成模块相关信息 if (!init_iff_file(stp, code, unloaded_size) || !scan_iff_file(stp, chunk_types, NUM_CHUNK_TYPES, NUM_MANDATORY) || !verify_chunks(stp)) { goto load_error; } /* * 读取代码块头部信息,检查版本支持,获取label和函数个数 */ CHKBLK(ERTS_ALC_T_CODE,stp->code); define_file(stp, "code chunk header", CODE_CHUNK); if (!read_code_header(stp)) { goto load_error; } /* * 初始化代码信息 */ stp->code_buffer_size = 2048 + stp->num_functions; stp->code = (BeamInstr *) erts_alloc(ERTS_ALC_T_CODE, sizeof(BeamInstr) * stp->code_buffer_size); stp->code[MI_NUM_FUNCTIONS] = stp->num_functions; stp->ci = MI_FUNCTIONS + stp->num_functions + 1; stp->code[MI_ATTR_PTR] = 0; stp->code[MI_ATTR_SIZE] = 0; stp->code[MI_ATTR_SIZE_ON_HEAP] = 0; stp->code[MI_COMPILE_PTR] = 0; stp->code[MI_COMPILE_SIZE] = 0; stp->code[MI_COMPILE_SIZE_ON_HEAP] = 0; /* * 读取原子表 */ CHKBLK(ERTS_ALC_T_CODE,stp->code); define_file(stp, "atom table", ATOM_CHUNK); if (!load_atom_table(stp)) { goto load_error; } /* * 读取导入函数表 */ CHKBLK(ERTS_ALC_T_CODE,stp->code); define_file(stp, "import table", IMP_CHUNK); if (!load_import_table(stp)) { goto load_error; } /* * 读取匿名函数 */ CHKBLK(ERTS_ALC_T_CODE,stp->code); if (stp->chunks[LAMBDA_CHUNK].size > 0) { define_file(stp, "lambda (fun) table", LAMBDA_CHUNK); if (!read_lambda_table(stp)) { goto load_error; } } /* * 读取数据表 */ CHKBLK(ERTS_ALC_T_CODE,stp->code); if (stp->chunks[LITERAL_CHUNK].size > 0) { define_file(stp, "literals table (constant pool)", LITERAL_CHUNK); if (!read_literal_table(stp)) { goto load_error; } } /* * 读取line信息 */ CHKBLK(ERTS_ALC_T_CODE,stp->code); if (stp->chunks[LINE_CHUNK].size > 0) { define_file(stp, "line table", LINE_CHUNK); if (!read_line_table(stp)) { goto load_error; } } /* * 加载代码块,生成label */ CHKBLK(ERTS_ALC_T_CODE,stp->code); stp->file_name = "code chunk"; stp->file_p = stp->code_start; stp->file_left = stp->code_size; if (!load_code(stp)) {// 加载代码 goto load_error; } CHKBLK(ERTS_ALC_T_CODE,stp->code); if (!freeze_code(stp)) { goto load_error; } /* * 读取和确认导出函数 */ CHKBLK(ERTS_ALC_T_CODE,stp->code); define_file(stp, "export table", EXP_CHUNK); if (!read_export_table(stp)) { goto load_error; } /* * Good so far. */ retval = NIL; load_error: if (retval != NIL) { free_loader_state(magic); } return retval; }
然后生成导出函数:
/* * beam_load.c read_export_table函数,生成导出函数 */ static int read_export_table(LoaderState* stp) { int i; BeamInstr* address; GetInt(stp, 4, stp->num_exps); if (stp->num_exps > stp->num_functions) { LoadError2(stp, "%d functions exported; only %d functions defined", stp->num_exps, stp->num_functions); } stp->export = (ExportEntry *) erts_alloc(ERTS_ALC_T_PREPARED_CODE, (stp->num_exps * sizeof(ExportEntry))); /* beam文件导出函数表的格式 & 4 bytes 'ExpT' chunk ID * 4 bytes size total chunk length * 4 bytes n number of entries * xx bytes ... Function entries (each 3 * 4 bytes): Function, Arity, Label */ for (i = 0; i < stp->num_exps; i++) { Uint n; Uint value; Eterm func; Uint arity; GetInt(stp, 4, n); GetAtom(stp, n, func); stp->export[i].function = func; GetInt(stp, 4, arity); if (arity > MAX_REG) { LoadError2(stp, "export table entry %d: absurdly high arity %d", i, arity); } stp->export[i].arity = arity; GetInt(stp, 4, n); if (n >= stp->num_labels) { LoadError3(stp, "export table entry %d: invalid label %d (highest defined label is %d)", i, n, stp->num_labels); } value = stp->labels[n].value; if (value == 0) { LoadError2(stp, "export table entry %d: label %d not resolved", i, n); } stp->export[i].address = address = stp->code + value; /* * Find out if there is a BIF with the same name. */ if (!is_bif(stp->module, func, arity)) { continue; } /* * This is a stub for a BIF. * * It should not be exported, and the information in its * func_info instruction should be invalidated so that it * can be filtered out by module_info(functions) and by * any other functions that walk through all local functions. */ if (stp->labels[n].patches) { LoadError3(stp, "there are local calls to the stub for " "the BIF %T:%T/%d", stp->module, func, arity); } stp->export[i].address = NULL; address[-1] = 0; address[-2] = NIL; address[-3] = NIL; } return 1; load_error: return 0; }
热更代码
紧接着看下erlang:finish_loading(),是个bif函数,实现代码更新
/* * beam_bif_load.c finish_loading_1()函数,实现更新代码到VM */ BIF_RETTYPE finish_loading_1(BIF_ALIST_1) { int i; int n; struct m* p = NULL; Uint exceptions; Eterm res; int is_blocking = 0; int do_commit = 0; /* * 获取代码修改权限,失败等下次调度再执行(保证同时只有一个进程能修改代码) */ if (!erts_try_seize_code_write_permission(BIF_P)) { ERTS_BIF_YIELD1(bif_export[BIF_finish_loading_1], BIF_P, BIF_ARG_1); } /* * 在加载代码前检验要加载的代码 */ n = list_length(BIF_ARG_1); if (n == -1) { ERTS_BIF_PREP_ERROR(res, BIF_P, BADARG); goto done; } p = erts_alloc(ERTS_ALC_T_LOADER_TMP, n*sizeof(struct m)); for (i = 0; i < n; i++) { Eterm* cons = list_val(BIF_ARG_1); Eterm term = CAR(cons); ProcBin* pb; if (!ERTS_TERM_IS_MAGIC_BINARY(term)) { ERTS_BIF_PREP_ERROR(res, BIF_P, BADARG); goto done; } pb = (ProcBin*) binary_val(term); p[i].code = pb->val; p[i].module = erts_module_for_prepared_code(p[i].code); if (p[i].module == NIL) { ERTS_BIF_PREP_ERROR(res, BIF_P, BADARG); goto done; } BIF_ARG_1 = CDR(cons); } /* * 目前只支持单个模块热更,以后可能会支持多个模块 */ if (n > 1) { ERTS_BIF_PREP_ERROR(res, BIF_P, SYSTEM_LIMIT); goto done; } /* * 到这里,代码检查已经完成,现在准备加载代码 * 要检查模块是否有旧代码,同时阻塞其他线程 */ res = am_ok; erts_start_staging_code_ix();// 使用下一个 code_index 的准备操作(后面讲解) for (i = 0; i < n; i++) { p[i].modp = erts_put_module(p[i].module); } for (i = 0; i < n; i++) { if (p[i].modp->curr.num_breakpoints > 0 || p[i].modp->curr.num_traced_exports > 0 || erts_is_default_trace_enabled()) { /* tracing involved, fallback with thread blocking */ erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN); erts_smp_thr_progress_block(); is_blocking = 1; break; } } if (is_blocking) { for (i = 0; i < n; i++) { if (p[i].modp->curr.num_breakpoints) { erts_clear_module_break(p[i].modp); ASSERT(p[i].modp->curr.num_breakpoints == 0); } } } // 检查旧代码是否还在使用(状态 not_purged) exceptions = 0; for (i = 0; i < n; i++) { p[i].exception = 0; if (p[i].modp->curr.code && p[i].modp->old.code) { p[i].exception = 1; exceptions++; } } if (exceptions) { res = exception_list(BIF_P, am_not_purged, p, exceptions); } else { /* * 现在开始加载代码(到这里就不会失败了) */ exceptions = 0; for (i = 0; i < n; i++) { Eterm mod; Eterm retval; erts_refc_inc(&p[i].code->refc, 1); retval = erts_finish_loading(p[i].code, BIF_P, 0, &mod); // 加载代码到VM ASSERT(retval == NIL || retval == am_on_load); if (retval == am_on_load) { p[i].exception = 1; exceptions++; } } if (exceptions) { res = exception_list(BIF_P, am_on_load, p, exceptions); } do_commit = 1; } done: // 加载代码完成,切换code index,恢复进程状态(前面阻塞了其他线程) return staging_epilogue(BIF_P, do_commit, res, is_blocking, p, n); }再看erts_finish_loading,实现加载代码到VM
/* * beam_load.c erts_finish_loading函数 */ Eterm erts_finish_loading(Binary* magic, Process* c_p, ErtsProcLocks c_p_locks, Eterm* modp) { Eterm retval; LoaderState* stp = ERTS_MAGIC_BIN_DATA(magic); /* * 准备更新导出函数表(没有加锁保护,确保SMP下其他线程已经被阻塞) */ ERTS_SMP_LC_ASSERT(erts_initialized == 0 || erts_has_code_write_permission() || erts_smp_thr_progress_is_blocking()); /* * 下面这一步,'current'版本代码将变成了'old'版本,新的代码就成了'current'版本 * 如果存在'old'版本代码,操作将失败 */ CHKBLK(ERTS_ALC_T_CODE,stp->code); retval = insert_new_code(c_p, c_p_locks, stp->group_leader, stp->module, stp->code, stp->loaded_size); if (retval != NIL) { goto load_error; } /* * 修正导出函数表入口 */ CHKBLK(ERTS_ALC_T_CODE,stp->code); final_touch(stp); /* * 加载完成(顺道打印调试信息) */ CHKBLK(ERTS_ALC_T_CODE,stp->code); #if defined(LOAD_MEMORY_HARD_DEBUG) && defined(DEBUG) erts_fprintf(stderr,"Loaded %T\n",*modp); #if 0 debug_dump_code(stp->code,stp->ci); #endif #endif stp->code = NULL; /* Prevent code from being freed. */ *modp = stp->module; /* * 如果存在 on_load 函数,抛出 on_load 必须运行的信号 */ if (stp->on_load) { retval = am_on_load; } load_error: // 释放代码数据 free_loader_state(magic); return retval; }下面看下insert_new_code函数,应该是热更新最核心的函数了。
/* * beam_load.c insert_new_code函数,实现加载代码到VM */ static Eterm insert_new_code(Process *c_p, ErtsProcLocks c_p_locks, Eterm group_leader, Eterm module, BeamInstr* code, Uint size) { Module* modp; Eterm retval; // 使'current'版本代码将变成了'old'版本 if ((retval = beam_make_current_old(c_p, c_p_locks, module)) != NIL) { erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf(); erts_dsprintf(dsbufp, "Module %T must be purged before loading\n", module); erts_send_error_to_logger(group_leader, dsbufp); return retval; } /* * 更新模块表,同时使新的代码就成为'current'版本 */ erts_total_code_size += size; modp = erts_put_module(module); modp->curr.code = code; modp->curr.code_length = size; modp->curr.catches = BEAM_CATCHES_NIL; /* Will be filled in later. */ /* * 更新模块代码地址范围(为了实现通过指令指针快速查找到函数) */ erts_update_ranges(code, size); return NIL; }
到这里,erlang的热更新实现代码基本讲完了,再来回顾下erlang代码热更新的过程。
1.编译erl成beam文件
2.清理模块(同时杀掉运行'old'代码的进程,'current'的不受影响)
3.加载beam代码到vm
而这里讨论了erlang代码更新的过程:
1、beam解析,生成代码,和导出函数。
2、加载代码,更新导出函数表,模块版本交替,切换主版本
探讨erlang热更新机制
首先要明白一个概念,erlang VM的实现基于寄存器,有400多条指令,这些指令包括了算术运算、比较和逻辑运算,操作字符串、元组和列表,堆栈的分配和释放,类型判断(数字、列表、元组等),跳转,异常处理,调用和返回,进程消息发送和接收,等待和超时等等。
erlang会将所有代码生成为基本指令操作,就是说执行一个函数,其实就是调转到某个地址后执行若干条指令。也就是基于指令地址寻址,然后执行一系列的指令,所以,只要把函数入口地址指向另外一个函数,就可以实现代码切换,形象点就是调用一个同名不同地址的函数。(这个利用c函数指针实现的,而指令跳转利用goto或者switch-case实现)
那么,修改代码不影响其他进程执行?为何进程还能访问旧代码?
VM利用 code index为代码保存了多个副本,进程当前执行的指令上下文都不会改变,使得进程执行不会受代码更新的影响。其中,只是函数入口地址变了,执行本地调用就不改变code index,执行原来函数的指令集合,外部调用就会获取最新的code index,执行到新的指令集合。关于code index 我也准备了满满的内容和大家分享。
延伸阅读
code index(代码索引)
VM为每份代码都保存了“多个副本”,然后通过一个全局的 code index 确认当前使用的是哪个版本。code index 作用是当 beam 代码正在修改时(如加载,更新,或删除),允许 erlang 进程同时访问执行代码而不用加锁。code index 同时作用于 export / module / beam_catches / beam_ranges 这几个模块的结构数据。
code index 有3个状态: active 、staging,和另外一个未明确使用的状态(可以理解成“上一个的active”,或者是“下一个staging”,可能以后会用到)。其中,active 表示当前使用的版本;staging表示下一个版本,仅在更新 beam 代码时使用到。当代码更新完成后 staging 将切换成 active,那active 就变成了“上一个active状态”。代码改变时就一直重复这个过程。
这里要明确一点,code index跟模块的 'current' 和'old' 版本不是一个概念,实际上是不相干的两个东西。
如何理解code index 的用途?
这个要从函数的调用过程说起,下面简单写个例子,保存为test.erl
-module(test). -compile(export_all). t() -> t2(). t1() -> ?MODULE:t2(). t2() -> erlang:memory().编译,生成opcode
1> c(test). {ok,test} 2> erts_debug:df(test). ok打开生成的 test.dis
04C84308: i_func_info_IaaI 0 test t 0 04C8431C: i_call_only_f test:t2/0 04C84324: i_func_info_IaaI 0 test t1 0 04C84338: i_call_ext_only_e test:t2/0 04C84340: i_func_info_IaaI 0 test t2 0 04C84354: i_call_ext_only_e erlang:memory/0 04C8435C: i_func_info_IaaI 0 test module_info 0 04C84370: move_cr test x(0) 04C84378: allocate_tt 0 1 04C84380: call_bif_e erlang:get_module_info/1 04C84388: deallocate_return_Q 0 04C84390: i_func_info_IaaI 0 test module_info 1 04C843A4: move_rx x(0) x(1) 04C843AC: move_cr test x(0) 04C843B4: allocate_tt 0 2 04C843BC: call_bif_e erlang:get_module_info/2 04C843C4: deallocate_return_Q 0可以看出,如果是本地函数调用,opcode是i_call_only_f ;如果是外部调用,opcode则是i_call_ext_only_e
下面从源码解释这两种调用的区别:
/* * beam_emu.c process_main() 线程入口函数,实现VM调度 * 以下截取 函数调用 处理过程 (已删除调试代码) */ OpCase(i_call_only_f): { SET_I((BeamInstr *) Arg(0)); Dispatch(); } OpCase(i_call_ext_only_e): Dispatchx();Dispatch() 和Dispatchx() 都是宏,再看下这两个的代码:
# define Dispatch() DispatchMacro() # define Dispatchx() DispatchMacrox()也就是下面2个宏:
/* * 检查是否有调度机会,有的话通过 I寄存器 跳转到指向的执行指令地址,没有的话切换上下文 */ #define DispatchMacro() \ do { \ BeamInstr* dis_next; \ dis_next = (BeamInstr *) *I; \ CHECK_ARGS(I); \ if (FCALLS > 0 || FCALLS > neg_o_reds) { \ FCALLS--; \ Goto(dis_next); \ } else { \ goto context_switch; \ } \ } while (0) #define DispatchMacrox() \ do { \ if (FCALLS > 0) { \ Eterm* dis_next; \ SET_I(((Export *) Arg(0))->addressv[erts_active_code_ix()]); \ dis_next = (Eterm *) *I; \ FCALLS--; \ CHECK_ARGS(I); \ Goto(dis_next); \ } else if (ERTS_PROC_GET_SAVED_CALLS_BUF(c_p) \ && FCALLS > neg_o_reds) { \ goto save_calls1; \ } else { \ SET_I(((Export *) Arg(0))->addressv[erts_active_code_ix()]); \ CHECK_ARGS(I); \ goto context_switch; \ } \ } while (0)前面也介绍了code index,可以知道,i_call_only_f 和i_call_ext_only_e 的主要区别是后者会重新获取最新的代码。也就是说,外部调用会执行到最新的代码。
这里需要说明几个关键信息,否能很难理解代码:
/* * I寄存器:指向下一条流程化指令的地址 */ register BeamInstr *I REG_I = NULL; /* * 剩余的reds数量,到达0时函数过程不再被执行,而是返回到调度器 */ register Sint FCALLS REG_fcalls = 0; #define Arg(N) I[(N)+1] #define SET_I(ip) \ ASSERT(VALID_INSTR(* (Eterm *)(ip))); \ I = (ip) #if defined(NO_JUMP_TABLE) // 没有跳转表,使用 switch-case # define Goto(Rel) {Go = (int)(Rel); goto emulator_loop;} #else // 有跳转表,使用 goto # define Goto(Rel) goto *((void *)Rel) #endif结合以上的定义,很多内容都很好理解,但是((Export *) Arg(0))->addressv[erts_active_code_ix()] 这个还是需要解释。
这里要知道addressv 的定义,这个字段是导出函数的代码地址, 在这里,实际地址指向了 ((Export*) Arg(0))-> code[3], 所以通知这个地址也可以知道是哪个函数调用。erts_active_code_ix() 表示了当前使用的 code index
/* * 导出函数的数据结构(export.h) */ typedef struct export { void* addressv[ERTS_NUM_CODE_IX]; // 函数代码地址 BeamInstr fake_op_func_info_for_hipe[2]; /* MUST be just before code[] */ /* * code[0]: 模块名 * code[1]: 函数名 * code[2]: 参数个数 * code[3]: 'address'字段没有指向它的时候是 0 ; * 否则就是函数流程化代码的指令地址 * code[4]: 指向bif函数地址 (仅BIFs), * 或者是指向函数流程化代码 (当 on_load 还没被执行时), * 或者是指向code[3] (如果是 breakpont指令时), * 默认是 0 */ BeamInstr code[5]; } Export;
估计还有同学很难明白,现在直接修改erlang源码演示这个问题:
OpCase(i_call_ext_only_e): // 加多下面这段代码 do { BeamInstr* fp1 = (BeamInstr *) (((Export *) Arg(0))->addressv[erts_active_code_ix()]); erts_fprintf(stderr,"*** mycwq debug *** %T:%T/%d code %p export %p\n", (Eterm)fp1[-3], (Eterm)fp1[-2], fp1[-1], fp1[0], Arg(0)); } while(0); Dispatchx();重新编译erlang源码,然后写个测试例子,内容如下,保存为 test.erl
-module(test). -compile(export_all). t2() -> t:tt(). start() -> Pid = spawn(fun() -> do_loop() end), register(t, Pid). do_loop() -> receive Msg -> io:format("~p~n", [Msg]) end, t2(), do_loop().
再写个程序,测试用, t.erl
-module(t). -compile(export_all). tt() -> erlang:memory(),ok.执行步骤如下(会有很多调试信息出来)
1> c(test). 2> c(t). 3> test:start(). 4> whereis(test)!any. 5> l(t). 6> whereis(test)!any.以上过程会打印刚刚在源码加上的调试信息,现在从调试信息中获取有用的数据,如下:
*** mycwq debug *** t:tt/0 code 0x000000000050e620 export 0x00002aaaae96fbd8
*** mycwq debug *** t:tt/0 code 0x000000000050c4d0 export 0x00002aaaae96fbd8
这里看出以上函数是一样,而且export 指针是一样的,都是0x00002aaaae96fbd8 ;只是执行的代码地址不同。如此说明,VM利用code index来切换代码版本
jump table (跳转表)
关于跳转表就写个例子容易解释,以下是c处理一个条件匹配的结构:
switch( Condition){ case Condition_A : .... case Condition_B : .... case Condition_C: .... } ;这种结构比较低效,经过汇编后,会产生如下的汇编代码:
cmp ....
jnz ....
cmp ....
jnz ....
cmp ....
jnz ....
比如,你有成千上万的 case 需要处理,而不幸的是最后一个case才匹配到,那么处理这个条件前会经过上万次的 cmp 与 jnz。
所以,高级编译器(如GCC)会引入跳转表的概念,把类似的条件进行聚类,减少比较的次数,然后从跳转表找到相应的位置跳转。
https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
另外,erlang对跳转表的应用不限于c层面上,在erlang层面也利用跳转表的思想和二分法来提交匹配效率(如case匹配)
结束语
最后,说个题外话,能否使用beam当作ets用?
如果不经常改可以考虑,正好利用到beam并发读效率,像是配置文件可以这么干。但是如果是动态内容,需要经常更新,就不能这么干了。首先,编译beam也有时间开销,数据越多编译时间越长。虽然热更代码时,当前的进程受到调度,但更新过程是bif操作,不会被切出,只要进程获得调度机会,在这个更新beam过程中,其他进程是不受调度的,erlang虽然保证热更新不影响所有进程执行,但是如果beam文件足够大,就会影响进程的并发性。而且,目前code index是全局性,就是说VM不可能并发修改代码。另外,热更代码时要检查是否有进程使用旧代码,就会遍历所有的进程,检查栈和外堆、消息队列。删除旧代码时会遍历整个导出函数表
所以,beam的并发修改比较弱,不适合存储频繁改变的数据。
2015/2/10 修改结束语中beam加载调度说明
2015/4/7 补充结束语中热更检查旧代码说明
参考:http://blog.csdn.net/mycwq/article/details/43372687
相关推荐
#### 三、Erlang热部署的实现原理 Erlang 的热部署是基于其模块化的设计思想实现的。每个Erlang程序由一个或多个模块组成,这些模块定义了函数和数据类型。当需要更新代码时,Erlang允许我们重新编译并加载新的模块...
Erlang热更新与Port驱动 - **Port驱动**: 特殊类型的Port,用于与操作系统直接交互。 - **热更新**: - **概念**: 不重启服务即可更新代码的能力。 - **实现**: 利用Erlang的代码加载机制实现。 - **应用场景**: ...
以上总结了Erlang深度分析的主要知识点,涵盖了虚拟机、性能分析、编码实践、分布式系统开发、内存管理、高可用性设计、网络通信、热部署、并发模型、本地接口设计、系统监控以及社区资源等多个方面。这些知识能够...
6. **热更新**:Erlang系统支持运行时代码替换,无需停止服务即可更新或升级系统,这对于保持服务连续性至关重要。 7. **OTP(Open Telecom Platform)**:OTP是Erlang的标准库,提供了一套用于构建可靠、可扩展、...
Erlang的热代码升级机制允许在不中断服务的情况下更新代码,这对于在线游戏至关重要,因为它保证了游戏的持续运行,同时允许开发者进行快速迭代和修复。 6. **错误处理与容错** Erlang的错误处理机制强调“失败早...
这个压缩包包含的是Erlang的部分源代码,尽管可能不完整,但仍然能够提供对Erlang语言实现机制的深入了解。 1. **Erlang的并发模型** Erlang的并发模型基于进程(processes),这些进程是轻量级的,并且具有内置的...
4. 热代码升级:Erlang支持在不中断服务的情况下更新代码,这是其在实时系统中的一大优势。源码中会展示如何实现这一特性,包括如何加载新版本的模块,以及如何平滑地过渡到新版本。 5. 错误处理和容错:Erlang的...
在分析Erlang源码时,重点关注以下方面: - **进程间的交互**:查看进程是如何创建、通信和结束的,理解消息传递的工作原理。 - **模块和函数**:理解模块的功能划分,以及函数如何协同工作完成任务。 - **错误处理...
6. 热代码升级:学习如何在不中断服务的情况下更新运行中的Erlang应用程序。 7. 源码分析:书中可能包含一些实际项目或示例代码,帮助读者深入理解Erlang和OTP的实践应用。 书中的"Manning.Erlang.and.OTP.in....
Erlang支持热代码升级,使得echatServer在运行过程中可以安全地更新代码而不会中断服务。这在维护和迭代服务器功能时非常有用。 6. **错误处理和容错机制** Erlang的“let it crash”哲学鼓励程序在遇到错误时...
Erlang支持热代码升级,可以在不中断服务的情况下更新运行中的系统。这一特性对于在线服务来说极其重要,可以避免停机带来的业务损失。 5. **并发与分布式** Erlang的进程间通信(IPC)基于消息传递,这种机制...
- **代码热更新**:Erlang支持在不中断服务的情况下更新代码,这对于需要长时间运行的服务来说非常重要。 - **软实时**:Erlang适合那些对响应时间有一定要求但又不要求极其严格的场景。 #### Erlang的应用领域 ...
3. **错误恢复**:Erlang支持热代码升级和容错机制,允许程序在运行时修改和升级,而且一个进程的失败不会影响其他进程,增强了系统的健壮性。 4. **分布式系统**:Erlang内置了对分布式计算的支持,节点间可以透明...
6. **游戏源码分析**:对于初学者来说,分析游戏服务器的源码可以帮助理解Erlang在实际项目中的应用,包括如何处理玩家输入、如何维持游戏世界的状态、如何实现网络通信协议等。 7. **学习路径**:初学者可以通过...
通过解压并编译otp_src_R16B03,开发者可以学习Erlang的实现细节,探索其并发模型和垃圾回收机制,也可以针对特定需求进行定制或扩展。同时,对于想要参与Erlang社区贡献的开发者来说,这是一个很好的起点,可以了解...
5. **热代码替换**:Erlang允许在运行时更新代码,无需停止服务,这对于维护和升级系统非常有利。 **随书源代码分析** `jaerlang-code.zip`文件包含了书中示例代码,这些代码可以帮助读者更好地理解书中的概念。...
5. **热代码升级**:Erlang 允许在运行时更新代码,无需停止服务,这对于维护和更新生产环境中的系统非常有用。 6. **OTP(Open Telecom Platform)**:OTP 是一组设计原则、库和工具,为Erlang应用提供了标准框架...
- **容错性**:Erlang支持热更新和软实时系统,能够很好地处理错误和异常情况,这对于构建高可用性和高可靠性的多核应用至关重要。 #### 三、Erlang多核编程实践案例分析 - **工业实例**:Erlang已经在多个行业中...
1. 数据存取:Erlang进程可以透明地读写MongoDB中的文档,实现游戏状态的实时更新。 2. 并发操作:Erlang的并发特性可以并行处理多个玩家的请求,同时与MongoDB进行数据交换,提高整体性能。 3. 错误处理:Erlang的...