数码资讯
erlang进程占用内存过多的查找问题过程
最近开发一个机器人程序,其主要功能是模拟玩家在地图上跑动用来测试地图压力。主要功能代码如下:
loop(State) ->
#state{robot_state = RobotState} = State,
case RobotState of
born ->
NewState = robot_born(State),
NextRobotState = get_next_robot_state(),
erlang:send_after(random:uniform(3000), self(), loop),
NewState#state{robot_state = NextRobotState};
move ->
robot_move(State),
State;
move_to_npc ->
robot_move_to_npc(State),
State;
_ ->
todo
end.
robot_move_to_npc(State) ->
#state{robot_player_id = RobotPlayerID, send_socket = _SendSocket} = State,
{CurMapDataID, CX, CY} = get_cur_robot_pos(State),
{TX, TY} = get_a_npc_point(CurMapDataID, CX, CY),
PathList = aStar:find_path(CurMapDataID, CX div 100, CY div 100, TX, TY),
case PathList of
[] ->
erlang:send_after(5000, self(), loop);
PathList ->
PosInfoList = [#pk_PosInfo{x = X * 100, y = Y * 100} || {X, Y} <- PathList],
debug:rpc_call(RobotPlayerID, pp_move, on_C2GS_PlayerMoveTo, [undefined, #pk_C2GS_PlayerMoveTo{posX = CX, posY = CY, curMapDataId = CurMapDataID,
posInfos = PosInfoList}]),
erlang:put(move_target, {TX, TY}),
erlang:send_after(1000, self(), {check_move_complete, 0})
end.
<span ></span><pre name="code" >get_a_npc_point(MapDataID, CX, CY) ->
<span > </span>NPCList = data_npc:get_items(),
<span > </span>F = fun(#npcCfg{map = Map}) ->
<span > </span>Map =:= MapDataID
<span > </span>end,
<span > </span>MapNPCList = lists:filter(F, NPCList),
<span > </span>#npcCfg{posx = TX, posy = TY} = lists:nth(random:uniform(length(MapNPCList)), MapNPCList),
<span > </span>{TargetX, TargetY} = mod_mirror:get_fix_point(CX div 100, CY div 100, TX, TY, 2, MapDataID),
<span > </span>{TargetX div 100, TargetY div 100}.
<span >如上所示,一个机器人就是一个单独的进程,机器人进程start起来后每隔一段loop,loop主要做的事情就是寻找一个点,并且让机器人移动过去。很简单的一个功能,但是出现了问题。在调试过程中发现一个机器人进程占用内存资源太多,居然有40M之多。在虚拟机上测试,1G内存在几秒时间内消耗90%以上。在手动gc的情况下,内存占用回到16M,但是还是太多。</span>
怀疑自己写的代码有问题,祭出神器——逐层扫描调试。首先把loop的主要功能屏蔽,机器人进程不干任何事情,process_info(PID, memory)查看内存占用,意料之中得到{memory,2560}。
第二步,屏蔽掉A*寻路过程,留下寻找目标点的过程。部分函数修改如下
robot_move_to_npc(State) ->
#state{robot_player_id = RobotPlayerID, send_socket = _SendSocket} = State,
{CurMapDataID, CX, CY} = get_cur_robot_pos(State),
{TX, TY} = get_a_npc_point(CurMapDataID, CX, CY),
todo.查看内存占用大概20M左右,说明在寻找目标点的函数get_a_npc_point/3有大量内存消耗。
第三步,进入get_a_npc_point/3,继续第二步方法,拆分每个语句,查看占用情况,修改如下
get_a_npc_point(MapDataID, _CX, _CY) ->
NPCList = data_npc:get_items(),
F = fun(#npcCfg{map = Map}) ->
Map =:= MapDataID
end,
MapNPCList = lists:filter(F, NPCList),
#npcCfg{posx = TX, posy = TY} = lists:nth(random:uniform(length(MapNPCList)), MapNPCList),
{TX, TY}.修改之后,内存占用情况大为好转,显然问题在mod_mirror:get_fix_point这个方法上。这个方法的目的是根据给定的一个点,随机返回其周围八个方向的一个可以行走的点。在判断随机出来的点是否可以行走时,会根据给定的MapID在地图数据ETS表中查找该地图中所有不可走的点来做判断。由于ets:lookup/2查找出来地图数据结构过大,EVM在执行完该ets:lookup/2之后,需要在进程中开辟出更多的空间来存储,所以进程占用的内存就变多了。但是这种机制是erlang本身的内存管理方式,程序猿并没办法改变。
结论:1、erlang每次执行的结果都会在进程里开辟空间来存储,如果执行结果赋值给了一个变量,那么该变量只是执行结果所在内存的一个指针。2、如果一个进程内存能通过手动gc明显降下来,说明很可能和你的代码无关,是执行结果太过大。3、如果在这种情况下需要减少进程占用的内存空间,可以在代码里面添加erlang:garbage_collect,前提是你的进程有足够的处理时间来做gc(在这个内存不值钱的年代不提议这样做,尤其是在对处理速度要求非常高的情况下)。
附:
1、初始占用内存
(gameserver_1@localhost)8> erlang:process_info(erlang:whereis(robot_10_1), memory).
{memory,33657600}
2、删除mod_mirror:get_fix_point这个方法后占用内存
(gameserver_1@localhost)16> erlang:process_info(erlang:whereis(robot_10_1), memory).
{memory,2543448}
3、不删除mod_mirror:get_fix_point这个方法,增加erlang:garbage_collect的内存占用
(gameserver_1@localhost)22> erlang:process_info(erlang:whereis(robot_10_54), memory). {memory,4714992}