本站首页    管理页面    写新日志    退出


«October 2025»
1234
567891011
12131415161718
19202122232425
262728293031


公告
 本博客在此声明所有文章均为转摘,只做资料收集使用。

我的分类(专题)

日志更新

最新评论

留言板

链接

Blog信息
blog名称:
日志总数:1304
评论数量:2242
留言数量:5
访问次数:7636657
建立时间:2006年5月29日




[Python]profile优化实践(基于A*算法)-1
软件技术

lhwork 发表于 2007/2/6 9:05:26

在《用profile协助程序性能优化》一文中,我们学习了python用以协助性能优化的模块——profile/hotshot/timeit等,但缺少一个实例来让我们动手尝试,今天我拿以前写的A*算法的python实现来开刀,临床实验。 实验用的代码可以从我以前发在blog上的文章《基本A*算法python实现》(http://blog.csdn.net/lanphaday/archive/2006/10/11/1329956.aspx)里找到,你可以从那里了解或者重温一下A*算法;不过为了使用profile,我做了一些小小的改动,所以建议你从这里(暂未能提供)下载本文相关的所有示例代码(已将每一次改进保存到独立的.py文件)以及profile的输出文档。 下面,让我们开始吧!   得来全不费功夫 定位热点        拿到代码后,可以看到代码的入口如下: if __name__ == "__main__": import profile, pstats profile.run("main()", "astar_prof.txt") p = pstats.Stats("astar_prof.txt") p.strip_dirs().sort_stats("time").print_stats(10) 代码段1 profile.run执行main()函数,并把输出保存到astar_prof.txt,pstats.Stats的实例p把统计结果以”time”为key排序后打印出前10条。执行一下,输出结果如下:          62468 function calls in 1.258 CPU seconds      Ordered by: internal time    List reduced from 27 to 10 due to restriction <10>      ncalls  tottime  percall  cumtime  percall filename:lineno(function)      5713    0.581    0.000    0.581    0.000 origine.py:156(node_in_close)     33818    0.208    0.000    0.208    0.000 origine.py:111(get_dist)       778    0.178    0.000    0.387    0.000 origine.py:99(get_best)       778    0.150    0.000    0.851    0.001 origine.py:119(extend_round)      2933    0.048    0.000    0.048    0.000 origine.py:162(node_in_open)      6224    0.028    0.000    0.028    0.000 origine.py:168(is_valid_coord)      5714    0.022    0.000    0.022    0.000 origine.py:46(__init__)      5713    0.021    0.000    0.021    0.000 origine.py:148(get_cost)         1    0.016    0.016    1.256    1.256 origine.py:71(find_path)       778    0.003    0.000    0.003    0.000 origine.py:96(is_target) 输出1 其中node_in_close()函数用以检测可扩展的节点是否在close表中,这个简单的函数占用了46%的运行时间,差不多是排第二的get_dist()函数的三倍,我们的优化显然应该从node_in_colse()入手。优化一个函数,第一个方法应该是减少它的调用次数,然后才是优化这个函数本身,所以我们先在“代码段1”后面加入一条语句,用以查看哪个函数调用了node_in_close(): p.print_callers("node_in_close")输出结果如下:    Ordered by: internal time    List reduced from 27 to 1 due to restriction <'node_in_close'>   Function                       was called by... origine.py:156(node_in_close)   origine.py:119(extend_round)(5713)    0.851 输出2 我们看到只有extend_round函数调用了node_in_close(),看起来情况相当简明,extend_round()函数用以扩展搜索空间,有关node_in_close的代码段如下:             #构造新的节点             node = Node_Elem(p, new_x, new_y, p.dist+self.get_cost(                         p.x, p.y, new_x, new_y))             #新节点在关闭列表,则忽略             if self.node_in_close(node):                 continue 代码段2 从“代码段2”可以看到根据A*算法我们必然要检测node是否在close表中,所以无法在extend_round()中减少对node_in_close()函数的调用了。那我们只好在node_in_close()函数里找突破口:     def node_in_close(self, node):         for i in self.close:             if node.x == i.x and node.y == i.y:                 return True         return False 代码段3 马上可以看出这是一个对list的线性查找,在close表变得很大的时候,这种复杂度为O(N)的线性算法是相当耗时的,如果能转化为O(logN)的算法那就能节省不少时间了。O(logN)的查找算法基于两个数据结构,一个是有序表,另一个是二叉查找树。显然,对于频繁插入而不删除元素的list保持有序的代价非常大,使用查找树是我们更好的选择。 修改代码        在程序里加入二叉查找树支持非常简单,python2.3开始增加了sets模块,提供了以RB_tree为底层数据结构的Set类: from sets import Set as set然后在把close表初始化为一个空set self.close = set()把self.close.append(p)语句替换为: self.close.add(p)最关键的是重写node_in_close()函数为:        def node_in_close(self, node):               return  node in self.close 代码段4 简简单单就可以了,但这时候程序还不能运行,因为原来的Node_Elem类并不支持__hash__和__eq__函数,这样set就无法构造也无法查找元素了,所以最后一步是为Node_Elem增加这两个函数: class Node_Elem:        def __init__(self, parent, x, y, dist):         # …略               self.hv = (x << 16) ^ y                      def __eq__(self, other):               return self.hv == other.hv                      def __hash__(self):               return self.hv 代码段5 在构造函数中,我们加了一句self.hv=(x<<8)^y来计算一个Node_Elem元素的hash值,因为坐标相同的两个节点我们认为是相等的且坐标数值不会很大,所以这个hash函数可以保证不会产生冲突。大功告成之后我们运行一下看看:          78230 function calls in 0.845 CPU seconds      Ordered by: internal time    List reduced from 33 to 10 due to restriction <10>      ncalls  tottime  percall  cumtime  percall filename:lineno(function)     33818    0.205    0.000    0.205    0.000 astar.py:120(get_dist)       778    0.179    0.000    0.383    0.000 astar.py:108(get_best)       778    0.150    0.000    0.426    0.001 astar.py:128(extend_round)      5713    0.068    0.000    0.097    0.000 sets.py:292(__contains__)      5713    0.052    0.000    0.149    0.000 astar.py:165(node_in_close)      2933    0.050    0.000    0.050    0.000 astar.py:172(node_in_open)      6224    0.028    0.000    0.028    0.000 astar.py:178(is_valid_coord)      5714    0.028    0.000    0.028    0.000 astar.py:48(__init__)      6490    0.021    0.000    0.021    0.000 astar.py:58(__hash__)      5713    0.021    0.000    0.021    0.000 astar.py:157(get_cost) 输出3 我们可以看到,总的运行时间已经从1.258s下降到0.845s,而且node_in_close函数占用的时间已经相当少,不过因为node_in_close只在一个地方被调用,而且函数体本身就非常简单,那么我们可以去掉这个函数,直接在extend_round里进行判断,可以省下几千次函数调用的时间。        在这一步优化里,我们可以看到使用合适的数据结构可以增进数十倍的性能(使用list用时0.581s,使用set用时0.052s),随着地图的增大,这个比例将会更大。而profile也在这一个小节里初显身手,下一步又该怎么样去优化呢?


阅读全文(1691) | 回复(0) | 编辑 | 精华
 



发表评论:
昵称:
密码:
主页:
标题:
验证码:  (不区分大小写,请仔细填写,输错需重写评论内容!)



站点首页 | 联系我们 | 博客注册 | 博客登陆

Sponsored By W3CHINA
W3CHINA Blog 0.8 Processed in 0.207 second(s), page refreshed 144795940 times.
《全国人大常委会关于维护互联网安全的决定》  《计算机信息网络国际联网安全保护管理办法》
苏ICP备05006046号