erlang下A*算法的效率优化过程

近期在做一个系统时发现项目中用的A*算法效率太差了,具体要时间上的话就是一个100路径点的寻路要花上大概50ms左右,在需要大量寻路的时候会非常卡。

1、首先分析A*算法的过程,摘自百度百科http://baike.baidu.com/view/7850.htm?fr=aladdin

while (OPEN!=NULL)
{
从OPEN表中取估价值f(n)最小的节点n;
if (n节点==目标节点)
break ;
for (当前节点n的每个子节点X)
{
算X的估价值;
if (XinOPEN)
if (X的估价值小于OPEN表的估价值)
{
把n设置为X的父亲;
更新OPEN表中的估价值; //取最小路径的估价值
}
if (XinCLOSE)
continue ;
if (Xnotinboth)
{
把n设置为X的父亲;
求X的估价值;
并将X插入OPEN表中; //还没有排序
}
} //endfor
将n节点插入CLOSE表中;
按照估价值将OPEN表中的节点排序; //实际上是比较OPEN表内节点f的大小,从最小路径的节点向下进行。
} //endwhile(OPEN!=NULL)

2、启用fprof,查看各个操作具体耗时
fprof_start(Procs) ->
	[fprof:trace([start, {file, "/tmp/fprof.trace"}, {procs, Proc}]) || Proc <- Procs].

fprof_stop() ->
	Now = fun() ->
				  {A, B, _} = erlang:now(),
				  A * 1000000 + B
		  end,
	ok = fprof:trace(stop),
        ok = fprof:profile({file, "/tmp/fprof.trace"}),
        Analyse = lists:concat(["/tmp/fprof-", Now(), ".analysis"]),
        ok = fprof:analyse([{dest, Analyse}, {details, true}, {totals, true}, {sort, own}]),
        ok.

上述方法就是一次fprof启动和终止过程,对于过程内的每一次调用都会记录到指定的文件中,具体可以查看手册中关于fprof的用法。

3、查看生成的文件内容,找出耗时的具体函数
%                                               CNT       ACC       OWN
[{ totals,                                     28740,31024.155,  181.700}].  %%%

{[{{aStar,is_tile_walkable,3},                  581,  113.807,   65.051},
  {{lists,member,2},                            458,    0.000,   42.260}],
 { {lists,member,2},                           1039,  113.807,  107.311},     %
 [{{lists,member,2},                            458,    0.000,   42.260},
  {suspend,                                     458,    6.496,    0.000}]}.

{[{{lists,foldl,3},                            3810,    0.000,    9.036},
  {{aStar,insert_open_list,1},                  327,   15.254,    0.656},
  {{erl_lint,bool_option,4},                     26,    0.185,    0.053},
  {{orddict,from_list,1},                         4,    0.119,    0.024},
  {{erl_eval,merge_bindings,2},                   7,    0.133,    0.014},
  {{dict,from_list,1},                            6,    3.200,    0.012},
  {{erl_lint,value_option,7},                     2,    0.014,    0.004},
  {{erl_lint,exprs_opt,3},                        2,    0.020,    0.004},
  {{erl_lint,expr_list,3},                        2,    0.042,    0.003}],

从上面的内容可以看出,一次操作中,aStar:is_tile_walkable这个方法调用次数最多,占用时间也是最多,其中最主要的消耗在于lists:member。出现上述这种情况是因为,对于算法中需要探测的每一个点,都要检测其周围的点是否是可走的点,如果是可走的点才会加入openlist中。判断一个点是否可走的方法是,查看该点是否在不可走点的列表中。不可走点的列表是{X, Y}类数据的tuplelist。

4、针对lists:member这个最耗时操作优化
由于函数lists:member(Item, List)是依次用List中的每一个元素去检查是否匹配Item,所以对于{X, Y}这种结构的匹配比一个单独X要复杂,所以先把不可走点列表中的{X, Y}结构转换为X,具体转换规则可以自由定制。这样改完之后,原本50ms的时间减小到了30ms。

5、进一步优化
30ms对于一些情况下还是太过耗时了,而整个算法最耗时操作在lists:member,这种操作的时间复杂度是o(n),如果能改结构呢?用二叉树存储不可走点列表,那么理论上复杂度就是o(logn)。修改结构,使用gb_sets存储,再次检查时间消耗只有2ms。
结论:1、list是erlang中常用的结构,erlang很多操作是通过遍历list实现的,但是如果在数据量上升到100以上,效率会下降很多。2、erlang中自带的二叉树结构有gb_sets,gb_trees,对于处理大量数据的查找、插入很有效。3、fprof这个工具在分析性能上非常好用,结合timer:tc可以很快找到问题。
附:A*算法代码
%% Author: pyf
%% Created: 2014-8-3
%% Description: A*算法
-module(aStar).

%%
%% Include files
%%
-include("logger.hrl").
-include("mapDefine.hrl").
-record(r_map_node, {key, g, f, p_parent}).
-define(close_list_length, 400).

%%
%% Exported Functions
%%
-export([find_path/4,
		 find_path/5]).

%%
%% API Functions
%%
%%Astar算法寻找路径,地图相关进程调用
find_path(StartX, StartY, EndX, EndY) ->
	case StartX =:= EndX andalso StartY =:= EndY of
		true ->
			[{StartX, StartY}];
		false ->
			UnWalkAbleTile = make_unwalkable_tile(),
			find_path2(StartX, StartY, EndX, EndY, UnWalkAbleTile)
	end.

find_path(MapDataID, StartX, StartY, EndX, EndY) ->
	case StartX =:= EndX andalso StartY =:= EndY of
		true ->
			[{StartX, StartY}];
		false ->
			UnWalkAbleTile = make_unwalkable_tile(MapDataID),
			find_path2(StartX, StartY, EndX, EndY, UnWalkAbleTile)
	end.

%%初始化起始点StartNode,初始化openlist和closelist
find_path2(StartX, StartY, EndX, EndY, UnWalkAbleTile) ->
	StartNode = #r_map_node{key = {StartX, StartY}, g = 0},
	set_open_list([]),
	set_close_list([]),
	find_path3(EndX, EndY, StartNode, UnWalkAbleTile).

find_path3(EndX, EndY, CurNode, UnWakeAbleTile) ->
	case catch insert_aound_map_nodes(CurNode, EndX, EndY, UnWakeAbleTile) of
		{ok, get_it} ->
			Path = [{EndX, EndY}],
            find_path4(CurNode, Path);
		Bool when is_atom(Bool) ->
			[MinNode | T] = get_open_list(),
			set_open_list(T),
			Bool andalso insert_close_list(CurNode, get_close_list()),
			find_path3(EndX, EndY, MinNode, UnWakeAbleTile);
		{failed} ->
			[];
		Other ->
			?ERR(astar, "~w", [Other])
	end.

find_path4(#r_map_node{p_parent = undefined} = MapNode, Path) ->
	open_list_delete(),
    close_list_delete(),
    [MapNode#r_map_node.key | Path];
find_path4(CurNode, Path) ->
    ParentNode = get_close_list(CurNode#r_map_node.p_parent),
    find_path4(ParentNode, [CurNode#r_map_node.key | Path]).



%%
%% Local Functions
%%
%%构造不可走的点,为了方便测试,这里随机构造100个点,其中不包括测试起始点{0, 0}和测试终止点{100, 100}
make_unwalkable_tile() ->
	mapViewConst:block2c_list().
%% 	F = fun(_N, AccIn) ->
%% 				[{random:uniform(100), random:uniform(100)} | AccIn]
%% 		end,
%% 	lists:foldl(F, [], lists:seq(1, 2000)).

make_unwalkable_tile(MapDataID) ->
	case get({?MapView_PhyList2C, MapDataID}) of
		undefined ->
			MapView = mapManager:getMapView(MapDataID),
			put(?MapView_LogicCellNumX, MapView#mapView.logicCellNumX),
			F = fun(CurIndex, Elem, Acc) ->
						  case Elem =:= true of
							  true ->
								  [CurIndex | Acc];
							  false ->
								  Acc
						  end
				  end,
			TileList = array:foldl(F, [], MapView#mapView.phyArray),
			GBTileList = gb_sets:from_list(TileList),
			put({?MapView_PhyList2C, MapDataID}, GBTileList),
			GBTileList;
		TileList ->
			TileList
	end.

%%计算从点{X, Y}到{X1, Y1}的期望值
get_qi_wang_zhi(X, Y, X1, Y1) ->
	erlang:abs(X1 - X) + erlang:abs(Y1 - Y).

%%插入元素Element到closelist中
insert_close_list(Element, CloseList) ->
	case length(CloseList) >= ?close_list_length of
		true ->
			throw({failed});
		false ->
			set_close_list([Element | CloseList])
	end.

%%更新openlist
update_open_list(MapNode) ->
	OpenList = get_open_list(),
	F = fun(#r_map_node{key = Key}, {AccInH, AccInT}) ->
				[AccInTH | AccInTT] = AccInT,
				case Key =:= MapNode#r_map_node.key of
					true ->
						throw({get, lists:reverse([MapNode | AccInH], AccInTT)});
					false ->
						{[AccInTH | AccInH], AccInTT}
				end
		end,
	case catch lists:foldl(F, {[], OpenList}, OpenList) of
		{get, NewOpenList} ->
			set_open_list(NewOpenList);
		_ ->
			set_open_list(OpenList)
	end.

insert_open_list(MapNode) ->
	OpenList = get_open_list(),
	F = fun(#r_map_node{f = F}, {AccInH, AccInT}) ->
				case F > MapNode#r_map_node.f of
					true ->
						throw({get, lists:reverse([MapNode | AccInH], AccInT)});
					false ->
						[AccInTH | AccInTT] = AccInT,
						{[AccInTH | AccInH], AccInTT}
				end
		end,
	case catch lists:foldl(F, {[], OpenList}, OpenList) of
		{get, NewOpenList} ->
			set_open_list(NewOpenList);
		_ ->
			set_open_list(OpenList ++ [MapNode])
	end.

%%判断点是否可走
is_tile_walkable(TX, TY, UnWakeAbleTile) ->
%% 	not lists:member({TX, TY}, UnWakeAbleTile).
	NumX = get(?MapView_LogicCellNumX),
	Index = TY * NumX + TX,
	TX >= 0 andalso TY >= 0 andalso gb_sets:is_element(Index, UnWakeAbleTile) =/= true.

%%检查点{TX, TY}是否closelist中的元素
close_list_member({TX, TY}) ->
	CloseList = get_close_list(),
	lists:keyfind({TX, TY}, #r_map_node.key, CloseList) =/= false.

%%遍历当前点CurNode周围的每一个点{TX, TY},并且对将其插入到openlist或者更新openlist
insert_aound_map_nodes(CurNode, EndTX, EndTY, UnWakeAbleTile) ->
	#r_map_node{key = {CTX, CTY}} = CurNode,
	F = fun({TXAdd, TYAdd}, AccIn) ->
				TX = CTX + TXAdd,
				TY = CTY + TYAdd,
				if TX =:= EndTX andalso TY =:= EndTY ->
					   erlang:throw({ok, get_it});
				   TX =:= CTX andalso TY =:= CTY ->
					   ignore;
				   true ->
					   insert_aound_map_nodes(CurNode, EndTX, EndTY, TX, TY, close_list_member({TX, TY}), UnWakeAbleTile) orelse AccIn
				end
		end,
	lists:foldl(F, false, [{0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1}, {-1, 0}, {-1, 1}]).

insert_aound_map_nodes(_CurNode, _EndTX, _EndTY, _TX, _TY, true, _UnWakeAbleTile) ->
	false;
insert_aound_map_nodes(CurNode, EndTX, EndTY, TX, TY, false, UnWakeAbleTile) ->
	OpenList = get_open_list(),
	MapNode = new_map_node(TX, TY, CurNode, EndTX, EndTY),
	case lists:keyfind({TX, TY}, #r_map_node.key, OpenList) of
		false ->
			case is_tile_walkable(TX, TY, UnWakeAbleTile) of
				true ->
					insert_open_list(MapNode),
					true;
				false ->
					false
			end;
		OldMapNode ->
			%%如果新的路径点MapNode已经是openlist中的元素OldMapNode,则比较两个路径的估值f,openlist中保留f小的那个
			case MapNode#r_map_node.f >= OldMapNode#r_map_node.f of
				true ->
					false;
				false ->
					update_open_list(MapNode),
					true
			end
	end.

%%构造一个新的路径点
new_map_node(TX, TY, CurNode, EndTX, EndTY) ->
	G = CurNode#r_map_node.g + 1,
	H = get_qi_wang_zhi(TX, TY, EndTX, EndTY),
	#r_map_node{key={TX, TY}, g = G, f = G + H, p_parent = CurNode#r_map_node.key}.

get_open_list() ->
	erlang:get(astar_open_list).

set_open_list(OpenList) ->
	erlang:put(astar_open_list, OpenList).

open_list_delete() ->
	erlang:erase(astar_open_list).

get_close_list() ->
	erlang:get(astar_close_list).

get_close_list({TX, TY}) ->
	lists:keyfind({TX, TY},#r_map_node.key, get_close_list()).

set_close_list(CloseList) ->
	erlang:put(astar_close_list, CloseList).

close_list_delete() ->
	erlang:erase(astar_close_list).


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值