背景本文主要讨论python代码的性能分析和优化,旨在解决一些常见的语言层面的性能瓶颈。就是日常工作中的一些积累和总结,会比较基础和全面。顺便也会介绍一些服务架构方面的优化经验。一、Python语言背景Python简单易学,在数据计算和分析方面表现出色,催生了庞大的用户群体和活跃的社区,使其在数据分析、机器学习等领域具有先天优势。广泛的第三方支持,Python在在线服务上也被广泛使用。但是python在性能方面有着所有动态解释型高级语言的通病,也是制约python进一步广泛应用的重要因素。这也是这类解释型脚本语言的通病:2、从具体的业务应用场景来看,业务架构背景比较片面。下面我们结合我们目前的后端架构来看一下python性能瓶颈在业务应用中的具体表现。该系统是基于大数据和机器学习模型的在线风控系统。为大量金融机构提供风控服务。建模技术。这些也让我们基于python的服务性能面临着严峻的考验。下图是架构的简化图:代码优化的性能分析前提是知道性能瓶颈在哪里,程序运行的主要时间花在了哪里。一般可以在日志中统计运行时间。对于比较复杂的代码你也可以使用一些工具来定位。Python内置了丰富的性能分析工具,可以描述程序运行时的性能,并提供各种统计数据帮助用户定位程序的性能瓶颈。常用的profilers:cProfile、profile、line_profile、pprofile和hotshot等。当然pycharm等一些IDE也继承了perfectprofiling。这里只介绍几种有代表性的性能分析方法:1、使用装饰器实现函数的耗时统计。点装饰器通过闭包向原始函数添加新函数。Python可以使用装饰器作为语法糖。函数的耗时统计,但这仅限于一般的同步方式。在协程中,更一般的在生成器函数中,由于yield会释放当前线程,耗时统计会在执行到yield时中断返回。导致统计失败。下面是一个包含两层闭包的装饰器(因为需要给装饰器传递参数):deftime_consumer(module_name='public_module'):deftime_cost(func):#获取调用装饰器的函数路径filepath=sys._getframe(1).f_code.co_filename@wraps(func)defwrapper(*args,**kwargs):t1=time.time()res=func(*args,**kwargs)t2=time.time()content={}尝试:content['time_cost']=round(float(t2-t1),3)content['method']=func.__name__content['file']=filepath.split(os.sep)[-1]content['module']=module_namecontent_res=json.dumps(content)time_cost_logger.info(content_res)除了Exceptionase:time_cost_logger.warning('%sdetail:%s'%(str(e),traceback.format_exc()))returnresreturnwrapperreturntime_cost2.函数级性能分析工具cprofilecProfile从python2.5开始就是标准Python解释器的默认性能配置文件Analyzer,是一个确定性分析器,只测量CPU时间,不关心内存消耗等与内存相关的信息性能分析结果字段含义ncalls:函数被调用的次数。tottime:函数内部消耗的总时间。percall:每次通话花费的平均时间。cumtime:消费时间累计总和。filename:lineno(function):分析函数的文件名、行号、函数名。方法1.单个文件性能分析:python-mcProfile-stottimetest.py2.方法性能分析:importcProfiledeffunction():passif__name__=='__main__':cProfile.run("function()")3.项目中实时服务的性能分析:#一般需要绑定在服务框架的钩子函数中实现,下面两个方法分别放在入口钩子和出口钩子中;pstats格式化统计信息,并根据需要进行排序分析处理。def_start_profile(self):importcProfileself.pr=cProfile.Profile()self.pr.enable()def_print_profile_result(self):如果不是self.pr:returnself.pr.disable()importpstatsimportStringIOs=StringIO.StringIO()stats=pstats.Stats(self.pr,stream=s).strip_dirs().sort_stats('tottime')stats.print_stats(50)3.线级分析工具pprofile/line_profile使用line_profile和需要引入_kernprof__,所以这里选择pprofile。pprofile的效率虽然没有line_profile高,但是做性能分析的时候可以忽略。pprofile的用法与cprofile完全相同。性能分析结果字段含义Line:行号Hits:代码行执行次数Time:总执行时间Timeperhit:单次执行耗时%:耗时比例4.性能分析总结随便挑一个方便的易于使用的分析方法或工具。但总的思路是从整体到具体。例如,可以使用cprofile找出整个代码执行过程中耗时较长的函数,然后使用pprofile逐行分析这些函数,最终将代码的性能瓶颈精确定位到行级别。Python性能优化方法一、优化思路Python的性能优化方法有很多。具体问题可以从不同的角度去考虑和分析,但是可以归纳为两个思路:从服务架构和CPU效率的角度,将CPU密集型转为IO密集型优化。从代码执行和cpu利用率来看,需要提高代码性能和多核利用率。例如,基于此,python在线服务的优化思路可以从以下几个方面考虑:2、使用字典/集合等对哈希等数据结构的常见操作进行代码优化:检索、去重、交集、并集、差集1.在字典中/Searchincollection(后面代码中省略了计时部分)dic={str(k):1forkinxrange(1000000)}if'qth'indic.keys():passif'qth'indic:pass耗时:0.05900001525880.02,使用set求交集list1=list(range(10000))list2=[i*2foriinlist1]s1=set(list1)s2=set(list2)list3=[]#listIntersectionforkinlist1:ifkinlist2:list3.append(k)#time-consumedforsetintersections3=s1&s2:0.8199999332430.001000165939Ps:Set操作在去重方面表现突出,交集和差集:使用生成器代替可迭代对象,节省内存和计算资源。它不需要计算整个可迭代对象,只计算需要循环的部分。1.使用xrange代替range(python3中没有区别)foriinrange(1000000):passforiinxrange(1000000):pass耗时:0.08299994468690.03200006484992,listcomprehensionusinggeneratordic={str(k):1forkinxrange(100000)}list1=[kforkindic.keys()]list1=(kforkindic.keys())耗时:0.01300001144410.003000020980833,复杂逻辑生成的迭代对象使用函数而不是deffib(max)生成:n,a,b=0,0,1list=[]whilen
