@ -0,0 +1,149 @@ | |||
-module(main). | |||
-export([ | |||
server_start/0, | |||
server_stop/0, | |||
server_stop/1, | |||
is_running/0, | |||
is_running/1, | |||
reload/0, | |||
info/0, | |||
psl/0, | |||
psl/1, | |||
psl/2, | |||
get_info/0 | |||
]). | |||
-define(SERVER_APPS, [sasl, crypto, inets, ranch, os_mon, hot]). | |||
server_start()-> | |||
ok = application:load(hot), | |||
app_misc:init(), | |||
ok = start_applications(?SERVER_APPS). | |||
%% 加载更新 | |||
reload() -> | |||
reloader:reload_all(). | |||
%%停止游戏服务器 | |||
server_stop() -> | |||
server_stop(30). | |||
is_running() -> | |||
is_running(node()). | |||
is_running(Node) -> | |||
node_misc:is_process_running(Node, server). | |||
%%停止游戏服务器 | |||
server_stop(_SleepSeconds) -> | |||
app_misc:pause_accept(), | |||
stop_applications(?SERVER_APPS), | |||
ok. | |||
info() -> | |||
io:format( "abormal termination: | |||
~n Scheduler id: ~p | |||
~n Num scheduler: ~p | |||
~n Process count: ~p | |||
~n Process limit: ~p | |||
~n Memory used by erlang processes: ~p | |||
~n Memory allocated by erlang processes: ~p | |||
~n The total amount of memory allocated: ~p | |||
~n", | |||
get_info()), | |||
ok. | |||
get_info() -> | |||
SchedId = erlang:system_info(scheduler_id), | |||
SchedNum = erlang:system_info(schedulers), | |||
ProcCount = erlang:system_info(process_count), | |||
ProcLimit = erlang:system_info(process_limit), | |||
ProcMemUsed = erlang:memory(processes_used), | |||
ProcMemAlloc = erlang:memory(processes), | |||
MemTot = erlang:memory(total), | |||
[SchedId, SchedNum, ProcCount, ProcLimit, | |||
ProcMemUsed, ProcMemAlloc, MemTot]. | |||
psl() -> | |||
psl(100). | |||
psl(Num) -> | |||
lists:foldl( | |||
fun(P, true)-> | |||
case erlang:process_info(P, message_queue_len) of | |||
{message_queue_len, Count} when Count > Num -> | |||
print_process_info(P), | |||
false; | |||
_ -> | |||
true | |||
end; | |||
(_, false) -> | |||
false | |||
end, true, erlang:processes()). | |||
psl(ProcessPid, Num) -> | |||
case erlang:process_info(ProcessPid, message_queue_len) of | |||
{message_queue_len, Count} when Count > Num -> | |||
print_process_info(ProcessPid); | |||
_ -> | |||
ok | |||
end. | |||
print_process_info(P) -> | |||
io:format("~n~n=====process info===~n" | |||
"~p~n~p~n~p~n~p~n~p~n~p~n~p~n~p~n~p~n~p~n~n~n", | |||
[P, | |||
erlang:process_info(P, registered_name), | |||
erlang:process_info(P, current_function), | |||
erlang:process_info(P, message_queue_len), | |||
erlang:process_info(P, status), | |||
erlang:process_info(P, suspending), | |||
erlang:process_info(P, last_calls), | |||
erlang:process_info(P, links), | |||
erlang:process_info(P, dictionary), | |||
erlang:process_info(P, current_stacktrace) | |||
]). | |||
%%############辅助调用函数############## | |||
manage_applications(Iterate, Do, Undo, SkipError, ErrorTag, Apps) -> | |||
Iterate(fun(App, Acc) -> | |||
case Do(App) of | |||
ok -> [App | Acc];%合拢 | |||
{error, {SkipError, _}} when is_atom(SkipError) -> | |||
Acc; | |||
{error, {Error, Reason}} when is_list(SkipError) -> | |||
case lists:member(Error, SkipError) of | |||
true -> | |||
Acc; | |||
false -> | |||
io:format( | |||
"App ~p, Reason ~p~n", [App, Reason]), | |||
lists:foreach(Undo, Acc), | |||
throw({error, {ErrorTag, App, Reason}}) | |||
end; | |||
{error, Reason} -> | |||
io:format("App ~p, Reason ~p~n", [App, Reason]), | |||
lists:foreach(Undo, Acc), | |||
throw({error, {ErrorTag, App, Reason}}) | |||
end | |||
end, [], Apps), | |||
ok. | |||
start_applications(Apps) -> | |||
manage_applications(fun lists:foldl/3, | |||
fun application:start/1, | |||
fun application:stop/1, | |||
[already_started, cannot_start_application], | |||
cannot_start_application, | |||
Apps). | |||
stop_applications(Apps) -> | |||
io:format("stop_applications stopping.~n",[]), | |||
manage_applications(fun lists:foldr/3, | |||
fun application:stop/1, | |||
fun application:start/1, | |||
not_started, | |||
cannot_stop_application, | |||
Apps). | |||
@ -0,0 +1,59 @@ | |||
-module(node_misc). | |||
-export([names/1, make/1, parts/1, cookie_hash/0, | |||
is_running/2, is_process_running/2]). | |||
-define(EPMD_TIMEOUT, 30000). | |||
names(Hostname) -> | |||
Self = self(), | |||
Ref = make_ref(), | |||
{Pid, MRef} = spawn_monitor( | |||
fun () -> | |||
Self ! {Ref, net_adm:names(Hostname)} | |||
end), | |||
timer:exit_after(?EPMD_TIMEOUT, Pid, timeout), | |||
receive | |||
{Ref, Names} -> | |||
erlang:demonitor(MRef, [flush]), | |||
Names; | |||
{'DOWN', MRef, process, Pid, Reason} -> | |||
{error, Reason} | |||
end. | |||
make({Prefix, Suffix}) -> | |||
list_to_atom(lists:append([Prefix, "@", Suffix])); | |||
make(NodeStr) -> | |||
make(parts(NodeStr)). | |||
parts(Node) when is_atom(Node) -> | |||
parts(atom_to_list(Node)); | |||
parts(NodeStr) -> | |||
case lists:splitwith(fun (E) -> E =/= $@ end, NodeStr) of | |||
{Prefix, []} -> | |||
{_, Suffix} = parts(node()), | |||
{Prefix, Suffix}; | |||
{Prefix, Suffix} -> | |||
{Prefix, tl(Suffix)} | |||
end. | |||
cookie_hash() -> | |||
base64:encode_to_string(erlang:md5(atom_to_list(erlang:get_cookie()))). | |||
is_running(Node, Application) -> | |||
case rpc:call(Node, app_utils, which_applications, []) of | |||
{badrpc, _} -> | |||
false; | |||
Apps -> | |||
proplists:is_defined(Application, Apps) | |||
end. | |||
is_process_running(Node, Process) -> | |||
case rpc:call(Node, erlang, whereis, [Process]) of | |||
{badrpc, _} -> | |||
false; | |||
undefined -> | |||
false; | |||
P when is_pid(P) -> | |||
true | |||
end. |
@ -0,0 +1,165 @@ | |||
%% @copyright 2007 Mochi Media, Inc. | |||
%% @author Matthew Dempsky <matthew@mochimedia.com> | |||
%% | |||
%% @doc Erlang module for automatically reloading modified modules | |||
%% during development. | |||
-module(reloader). | |||
-author("Matthew Dempsky <matthew@mochimedia.com>"). | |||
-include_lib("kernel/include/file.hrl"). | |||
-behaviour(gen_server). | |||
-export([start/0, start_link/0]). | |||
-export([stop/0]). | |||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). | |||
-export([all_changed/0]). | |||
-export([is_changed/1]). | |||
-export([reload_modules/1]). | |||
-export([reload_all/0]). | |||
-record(state, {last, tref}). | |||
%% External API | |||
%% @spec start() -> ServerRet | |||
%% @doc Start the reloader. | |||
start() -> | |||
gen_server:start({local, ?MODULE}, ?MODULE, [], []). | |||
%% @spec start_link() -> ServerRet | |||
%% @doc Start the reloader. | |||
start_link() -> | |||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). | |||
%% @spec stop() -> ok | |||
%% @doc Stop the reloader. | |||
stop() -> | |||
gen_server:call(?MODULE, stop). | |||
%% gen_server callbacks | |||
%% -define(RERODER_CHECK_TIME, 5000). | |||
%% @spec init([]) -> {ok, State} | |||
%% @doc gen_server init, opens the server in an initial state. | |||
init([]) -> | |||
%% {ok, TRef} = timer:send_interval(timer:seconds(1), doit), | |||
%% TimerRef = erlang:send_after(?RERODER_CHECK_TIME, self(), doit), | |||
%% tref = TimerRef}}. | |||
{ok, #state{last = stamp()}}. | |||
%% @spec handle_call(Args, From, State) -> tuple() | |||
%% @doc gen_server callback. | |||
handle_call(stop, _From, State) -> | |||
{stop, shutdown, stopped, State}; | |||
handle_call(_Req, _From, State) -> | |||
{reply, {error, badrequest}, State}. | |||
%% @spec handle_cast(Cast, State) -> tuple() | |||
%% @doc gen_server callback. | |||
%% @spec handle_info(Info, State) -> tuple() | |||
%% @doc gen_server callback. | |||
handle_cast(doit, State) -> | |||
error_logger:info_msg("reloader do reload ... ~n", []), | |||
%% TimerRef = erlang:send_after(?RERODER_CHECK_TIME, self(), doit), | |||
Now = stamp(), | |||
try | |||
_ = doit(State#state.last, Now), | |||
%% tref = TimerRef | |||
error_logger:info_msg("reloader done ... ~n", []), | |||
{noreply, State#state{last = Now}} | |||
catch | |||
_:R -> | |||
error_logger:error_msg( | |||
"reload failed R:~w Stack:~p~n", [R, erlang:get_stacktrace()]), | |||
%% reloader failed, no state update | |||
{noreply, State} | |||
end; | |||
handle_cast(_Req, State) -> | |||
{noreply, State}. | |||
handle_info(_Info, State) -> | |||
{noreply, State}. | |||
%% @spec terminate(Reason, State) -> ok | |||
%% @doc gen_server termination callback. | |||
terminate(_Reason, _State) -> | |||
%% erlang:cancel_timer(State#state.tref), | |||
%% {ok, cancel} = timer:cancel(State#state.tref), | |||
ok. | |||
%% @spec code_change(_OldVsn, State, _Extra) -> State | |||
%% @doc gen_server code_change callback (trivial). | |||
code_change(_Vsn, State, _Extra) -> | |||
{ok, State}. | |||
%% @spec reload_modules([atom()]) -> [{module, atom()} | {error, term()}] | |||
%% @doc code:purge/1 and code:load_file/1 the given list of modules in order, | |||
%% return the results of code:load_file/1. | |||
reload_modules(Modules) -> | |||
[begin code:purge(M), code:load_file(M) end || M <- Modules]. | |||
%% @spec all_changed() -> [atom()] | |||
%% @doc Return a list of beam modules that have changed. | |||
all_changed() -> | |||
[M || {M, Fn} <- code:all_loaded(), is_list(Fn), is_changed(M)]. | |||
%% @spec reload_all() -> [atom()] | |||
reload_all() -> | |||
gen_server:cast(?MODULE, doit). | |||
%% @spec is_changed(atom()) -> boolean() | |||
%% @doc true if the loaded module is a beam with a vsn attribute | |||
%% and does not match the on-disk beam file, returns false otherwise. | |||
is_changed(M) -> | |||
try | |||
module_vsn(M:module_info()) =/= module_vsn(code:get_object_code(M)) | |||
catch _:_ -> | |||
false | |||
end. | |||
%% Internal API | |||
module_vsn({M, Beam, _Fn}) -> | |||
{ok, {M, Vsn}} = beam_lib:version(Beam), | |||
Vsn; | |||
module_vsn(L) when is_list(L) -> | |||
{_, Attrs} = lists:keyfind(attributes, 1, L), | |||
{_, Vsn} = lists:keyfind(vsn, 1, Attrs), | |||
Vsn. | |||
doit(From, To) -> | |||
[case file:read_file_info(Filename) of | |||
{ok, #file_info{mtime = Mtime}} when Mtime >= From, Mtime < To -> | |||
reload(Module); | |||
{ok, _} -> | |||
unmodified; | |||
{error, enoent} -> | |||
%% The Erlang compiler deletes existing .beam files if | |||
%% recompiling fails. Maybe it's worth spitting out a | |||
%% warning here, but I'd want to limit it to just once. | |||
gone; | |||
{error, Reason} -> | |||
error_logger:error_msg("Error reading ~s's file info: ~p~n", | |||
[Filename, Reason]), | |||
error | |||
end || {Module, Filename} <- code:all_loaded(), is_list(Filename)]. | |||
reload(Module) -> | |||
error_logger:info_msg("Reloading ~p ...", [Module]), | |||
code:purge(Module), | |||
case code:load_file(Module) of | |||
{module, Module} -> | |||
error_logger:info_msg("reload ~w ok.~n", [Module]), | |||
reload; | |||
{error, Reason} -> | |||
error_logger:error_msg("reload fail: ~p.~n", [Reason]), | |||
error | |||
end. | |||
stamp() -> | |||
erlang:localtime(). | |||
@ -0,0 +1,145 @@ | |||
-module(server_control_main). | |||
-export([start/0]). | |||
-define(RPC_TIMEOUT, infinity). | |||
commands_desc() -> | |||
[{"stop", "停止游戏服务器进程"}, | |||
{"stop_all", "停止游戏集群进程"}, | |||
{"stop_app", "关闭server application"}, | |||
{"start_app", "打开server application"}, | |||
{"cluster_status", "集群状态"}]. | |||
opt_spec_list() -> | |||
Node = case get(nodename) of | |||
undefined -> | |||
throw(not_nodename); | |||
V -> | |||
V | |||
end, | |||
[ | |||
{help, $h, "help", undefined, "显示帮助,然后退出"}, | |||
{node, undefined, "node", {atom, Node}, "管理节点"} | |||
]. | |||
usage() -> | |||
getopt:usage(opt_spec_list(), "server_ctl", "<command> [<args>]", commands_desc()), | |||
err_misc:quit(1). | |||
parse_arguments(CmdLine) -> | |||
case getopt:parse(opt_spec_list(), CmdLine) of | |||
{ok, {Opts, [Command | Args]}} -> | |||
{ok, {list_to_atom(Command), Opts, Args}}; | |||
{ok, {_Opts, []}} -> | |||
no_command; | |||
Error -> | |||
io:format("Error ~p~n", [Error]), | |||
no_command | |||
end. | |||
start() -> | |||
{ok, [[NodeStr|_]|_]} = init:get_argument(nodename), | |||
put(nodename, list_to_atom(NodeStr)), | |||
{Command, Opts, Args} = | |||
case parse_arguments(init:get_plain_arguments()) of | |||
{ok, Res} -> | |||
Res; | |||
no_command -> | |||
usage() | |||
end, | |||
Node = proplists:get_value(node, Opts), | |||
net_adm:ping(Node), | |||
timer:sleep(1000), %% wait auto find node | |||
%% The reason we don't use a try/catch here is that rpc:call turns | |||
%% thrown errors into normal return values | |||
% io:format("Opts ~p~n", [Opts]), | |||
case catch action(Command, Node, Args, Opts) of | |||
ok -> | |||
io:format("done.~n", []), | |||
quit(0); | |||
{ok, Info} -> | |||
io:format("done (~p).~n", [Info]), | |||
quit(0); | |||
Other -> | |||
io:format("other result ~p~n", [Other]), | |||
quit(2) | |||
end. | |||
action(info, Node, _Args, _Opts) -> | |||
io:format("System info for Node ~p~n", [Node]), | |||
Res = call(Node, {main, get_info, []}), | |||
io:format( " ~n Scheduler id: ~p | |||
~n Num scheduler: ~p | |||
~n Process count: ~p | |||
~n Process limit: ~p | |||
~n Memory used by erlang processes: ~p | |||
~n Memory allocated by erlang processes: ~p | |||
~n The total amount of memory allocated: ~p | |||
~n", | |||
Res), | |||
ok; | |||
action(backup, Node, _Args, _Opts) -> | |||
case call(Node, {app_misc, backup, []}) of | |||
{error, Msg} -> | |||
io:format("~s~n", [Msg]); | |||
{ok, FileName} -> | |||
io:format("backup file:~s~n", [FileName]), | |||
io:format("backup file to remote ......~n", []), | |||
Result = os:cmd("bash copy_to_remote.sh " ++ FileName), | |||
io:format("~s~n", [Result]) | |||
end, | |||
ok; | |||
action(pause_accept, Node, _Args, _Opts) -> | |||
io:format("Pause accept new client ~p~n", [Node]), | |||
call(Node, {app_misc, pause_accept, []}), | |||
ok; | |||
action(resume_accept, Node, _Args, _Opts) -> | |||
io:format("Resume accept new client ~p~n", [Node]), | |||
call(Node, {app_misc, resume_accept, []}), | |||
ok; | |||
action(accept_state, Node, _Args, _Opts) -> | |||
Res = call(Node, {app_misc, can_accept_new, []}), | |||
io:format("Node ~p accept state:~p~n ", [Node, Res]), | |||
ok; | |||
action(reload, Node, _Args, _Opts) -> | |||
io:format("Reloading node ~p~n", [Node]), | |||
call(Node, {main, reload, []}); | |||
action(stop_all, MasterNode, _Args, _Opts) -> | |||
io:format("Stopping and halting all node~n", []), | |||
PidMRefs = [{spawn_monitor(fun() -> | |||
call(Node, {main, stop_and_halt, [5]}) | |||
end), Node} | |||
|| Node <- nodes() -- [MasterNode]], | |||
[receive | |||
{'DOWN', MRef, process, _, normal} -> | |||
ok; | |||
{'DOWN', MRef, process, _, Reason} -> | |||
io:format("Node ~p Error, Reason ~p", [Node, Reason]) | |||
end || {{_Pid, MRef}, Node} <- PidMRefs], | |||
call(MasterNode, {main, stop_and_halt, [5]}), | |||
ok; | |||
action(stop, Node, _Args, _Opts) -> | |||
io:format("Stopping and halting node ~p~n", [Node]), | |||
call(Node, {main, stop_and_halt, [5]}); | |||
action(Command, _Node, Args, Opts) -> | |||
io:format("Command: ~p Args: ~p Opts: ~p~n", [Command, Args, Opts]), | |||
invalid_command. | |||
call(Node, {Mod, Fun, Args}) -> | |||
%%rpc_call(Node, Mod, Fun, lists:map(fun list_to_binary/1, Args)). | |||
rpc_call(Node, Mod, Fun, Args). | |||
rpc_call(Node, Mod, Fun, Args) -> | |||
rpc:call(Node, Mod, Fun, Args, ?RPC_TIMEOUT). | |||
quit(Status) -> | |||
case os:type() of | |||
{unix, _} -> | |||
halt(Status); | |||
{win32, _} -> | |||
init:stop(Status), | |||
receive | |||
after infinity -> | |||
ok | |||
end | |||
end. |
@ -0,0 +1,151 @@ | |||
%%---------------------------------------------------- | |||
%% Erlang模块热更新到所有线路(包括server的回调函数,如果对state有影响时慎用) | |||
%% | |||
%% 检查:u:c() %% 列出前5分钟内编译过的文件 | |||
%% u:c(N) %% 列出前N分钟内编译过的文件 | |||
%% | |||
%% 更新:u:u() %% 更新前5分钟内编译过的文件 | |||
%% u:u(N) %% 更新前N分钟内编译过的文件 | |||
%% u:u([mod_xx, ...]) %% 指定模块(不带后缀名) | |||
%% u:u(m) %% 编译并加载文件 | |||
%% | |||
%% Tips: u - update, c - check | |||
%% | |||
%% @author rolong@vip.qq.com | |||
%%---------------------------------------------------- | |||
-module(u). | |||
-compile(export_all). | |||
-include_lib("kernel/include/file.hrl"). | |||
c() -> | |||
c(5). | |||
c(S) when is_integer(S) -> | |||
c:cd("../ebin"), | |||
case file:list_dir(".") of | |||
{ok, FileList} -> | |||
Files = get_new_file(FileList, S * 60), | |||
info("---------check modules---------~n~w~n=========check modules=========", [Files]); | |||
Any -> info("Error Dir: ~w", [Any]) | |||
end; | |||
c([S]) when is_atom(S) -> | |||
S1 = tool:to_integer(tool:to_list(S)), | |||
case is_integer(S1) of | |||
true -> | |||
c:cd("../ebin"), | |||
case file:list_dir(".") of | |||
{ok, FileList} -> | |||
Files = get_new_file(FileList, S * 60), | |||
info("---------check modules---------~n~w~n=========check modules=========", [Files]); | |||
Any -> info("Error Dir: ~w", [Any]) | |||
end; | |||
_ -> | |||
info("ERROR======> Badarg ~p/~p ~n", [S, S1]) | |||
end; | |||
c(S) -> info("ERROR======> Badarg ~p ~n", [S]). | |||
admin()-> | |||
spawn(fun()->u(m) end), | |||
ok. | |||
u() -> | |||
u(5). | |||
u(m) -> | |||
StartTime = util:unixtime(), | |||
info("----------makes----------", []), | |||
c:cd("../"), | |||
make:all(), | |||
c:cd("ebin"), | |||
EndTime = util:unixtime(), | |||
Time = EndTime - StartTime, | |||
info("Make Time : ~w s", [Time]), | |||
u(Time / 60); | |||
u(S) when is_number(S) -> | |||
case file:list_dir(".") of | |||
{ok, FileList} -> | |||
Files = get_new_file(FileList, util:ceil(S * 60) + 3), | |||
load(Files); | |||
%% AllZone = mod_node_interface:server_list(), | |||
%% info("---------modules---------~n~w~n----------nodes----------", [Files]), | |||
%% loads(AllZone, Files); | |||
Any -> info("Error Dir: ~w", [Any]) | |||
end; | |||
u(Files) when is_list(Files) -> | |||
load(Files); | |||
%% AllZone = mod_node_interface:server_list(), | |||
%% info("---------modules---------~n~w~n----------nodes----------", [Files]), | |||
%% loads(AllZone, Files); | |||
u(_) -> info("ERROR======> Badarg", []). | |||
%% m(['src/data/*','src/lib/lib_goods.erl']) | |||
m(Files) when is_list(Files) -> | |||
StartTime = util:unixtime(), | |||
info("----------makes----------~n~w~n", [Files]), | |||
c:cd("../"), | |||
Res = make:files(Files, [debug_info,{i, "include"},{outdir, "ebin"}]), | |||
c:cd("ebin"), | |||
EndTime = util:unixtime(), | |||
Time = EndTime - StartTime, | |||
info("Make Time : ~w s", [Time]), | |||
Res. | |||
info(V) -> | |||
info(V, []). | |||
info(V, P) -> | |||
io:format(V ++ "~n", P). | |||
%% 更新到所有线路,暂时处理单节点的情况 | |||
%% loads([], _Files) -> ok; | |||
%% loads([H | T], Files) -> | |||
%% info("[~w]", [H#t_server_node.node]), | |||
%% rpc:cast(H#t_server_node.node, u, load, [Files]), | |||
%% loads(T, Files). | |||
get_new_file(Files, S) -> | |||
get_new_file(Files, S, []). | |||
get_new_file([], _S, Result) -> Result; | |||
get_new_file([H | T], S, Result) -> | |||
NewResult = case string:tokens(H, ".") of | |||
[Left, Right] when Right =:= "beam" -> | |||
case file:read_file_info(H) of | |||
{ok, FileInfo} -> | |||
Now = calendar:local_time(), | |||
case calendar:time_difference(FileInfo#file_info.mtime, Now) of | |||
{Days, Times} -> | |||
Seconds = calendar:time_to_seconds(Times), | |||
case Days =:= 0 andalso Seconds < S of | |||
true -> | |||
FileName = list_to_atom(Left), | |||
[FileName | Result]; | |||
false -> Result | |||
end; | |||
_ -> Result | |||
end; | |||
_ -> Result | |||
end; | |||
_ -> Result | |||
end, | |||
get_new_file(T, S, NewResult). | |||
load([]) -> ok; | |||
load([FileName | T]) -> | |||
c:l(FileName), | |||
info("loaded: ~w", [FileName]), | |||
load(T). | |||
% case code:soft_purge(FileName) of | |||
% true -> | |||
% case code:load_file(FileName) of | |||
% {module, _} -> | |||
% info("loaded: ~w", [FileName]), | |||
% ok; | |||
% %% info("loaded: ~w", [FileName]); | |||
% {error, What} -> info("ERROR======> loading: ~w (~w)", [FileName, What]) | |||
% end; | |||
% false -> info("ERROR======> Processes lingering : ~w [zone ~w] ", [FileName, srv_kernel:zone_id()]) | |||
% end, | |||
% load(T). | |||
a() -> | |||
c(), | |||
u(). |
@ -0,0 +1,35 @@ | |||
-module(utTestDS). | |||
-compile([export_all, nowarn_unused_function, nowarn_unused_vars, nowarn_export_all]). | |||
%% 用于测试erlang各种数据结构 读写遍历等操作的效率 | |||
%% lists ,maps 和record是erlang最为常用的数据结构,lists使用方便简单,maps则查询高效,record则需要预定义, | |||
%% 可扩展性差,各有各的优。本文做一下lists和maps的性能对比(再对比一下dict),代码如下(record操作不便则不做比较)。 | |||
%%通过注释部分代码做以下测试 | |||
%%timer:tc(lib_test, test_struct, [10000,#{}]). | |||
%%timer:tc(lib_test, test_struct, [10000,[]]). | |||
test_struct(0, R) -> | |||
Fun = fun({K, V}) -> K + 1, V + 1 end, lists:foreach(Fun, R), %%遍历测试 | |||
Fun = fun(K, V) -> K + 1, V + 1 end, maps:map(Fun, R), | |||
ok; | |||
test_struct(Num, R) -> | |||
NewR = [{Num, Num} | R], lists:keyfind(5000, 1, NewR), %%插入查询测试 | |||
NewR = R#{Num=>Num}, maps:get(5000, NewR, 0), | |||
test_struct(Num - 1, NewR). | |||
%% 做10000次的插入查询测试结果: | |||
%% | |||
%% lists 50736微秒 | |||
%% maps 4670微秒 | |||
%% dict 60236微秒 | |||
%% 做10000次的遍历结果: | |||
%% | |||
%% lists 523微秒 | |||
%% maps 8337微秒 | |||
%% dict 4426微秒 | |||
%% 对比总结: | |||
%% | |||
%% 对比测试数据maps在查询性能上比lists高10倍以上, 而在遍历上lists则更优。对于频繁插入和查询的数据,maps是最佳的选择, | |||
%% lists则适用于广播列表之类需要遍历的数据。除此之外,个人觉得在使用上lists 比maps更为方便,因为lists模块提供了大量实用的函数, | |||
%% 单单在排序上,maps的实用性就不如lists了,所以在数据结构选择上就需要多多斟酌。另外record在查询上使用的是模式匹配,性能只会更高, | |||
%% 但需要提前定义字段,可扩展性差,在热更这块有不少坑,maps也可以用模式匹配查询,但也要确保key值存在,不然就nomatch, | |||
%% 但整体上maps更优于record,故建议用maps替代record。 |