APP下载

阿里巴巴杨群:高并发场景下Python的效能挑战

消息来源:baojiabao.com 作者: 发布时间:2024-06-01

报价宝综合消息阿里巴巴杨群:高并发场景下Python的效能挑战

导读:阿里云数据库提供了稳定可靠、可弹性伸缩的线上数据库服务,在数据库储存引擎、云原生技术、分析引擎、分散式处理、FPGA/GPU硬件加速、智慧数据库、智慧化管控平台、安全数据库等方面有较好的表现。4月13日,在CSDN主办的“2019 Python开发者日”大会上,阿里云数据库专家杨群分享了《高并发场景下Python的效能挑战》的主题演讲。

以下为演讲整理,文章略有删减:

效能问题

▌(一)GIL

为什么大家都说Python慢?最主要的原因是全域性直译器锁。今天讲的Python是官方的C版Python。CPython在建立变数时,首先对变数分配内存,然后开始计数变数的数量,大家提出称之为“引用计数”。在引用计数变为0时,从系统中释放变数的内存。如果多个执行绪同时对这个计数做操作,执行绪不安全,会导致很多问题。

综合垃圾回收机制问题,CPython引入了GIL,同一个时刻在一个程序允许一个执行绪使用直译器,意味着单程序下Python多执行绪的效能没有那么好。这样做的好处在于能够避免死锁和资料使用者安全方面的问题。

Python有三种执行绪状态:Idle、Running、Failed GIL Acquire。曾经有人对GIL的效能影响做了两个测试。第一个测试案例是两个CPU密集执行绪,程式码执行过程的大部分状态是Failed GIL Acquire,两个执行绪的执行没有达到双核的效果。

第二个案例是IO密集型的执行绪。仔细分析发现,IO没有达到想象的预期效果。所以IO密集型和CPU密集型同时存在时,IO密集型未必达到想要的运算速度,我们要区分好IO密集型和CPU密集型的服务。

小编准备了一份Python学习资料,给那些正在学习Python的同学,或者准备学习Python的同学,关注,转发,私信小编“01”即可免费获取!

▌(二)直译器

CPython要首先生成pcy字节码序列,之后才能被CPU理解,所以较慢。JAVA、.NET也有中间的翻译,但因为JAVA和.NET使用即时编辑(JIT),使用JIT可以检测哪些程式码执行得比较多,意味着计算机应用程序需要重复做一件事情的时候它就会更快。

▌(三)动态语言

Python是动态语言型别,我们在做型别转化或者比较的时候比较耗时,因为读取、写入变数或者引用变数时会进行检查。静态型别语言没有这么高的灵活性,但它已经规定好了内存中的状态,所以很快。

Python这么慢,我们为什么还要用它?一是用Python优雅、简洁。二是大多数应用场景时,GIL或者直译器带来的效能未必是我们所担心的,比如科学计算或者平常做一些资料分析或小应用时不会考虑到这个问题。

服务选型

这是市面上常用的web框架针对Python的领域做服务选型分析的框架。无论使用什么web框架,在web服务中都会选择多程序。一方面考虑到服务需要一定的可用性,需要多程序来保证减少服务可用性的影响。另外,多个程序意味着多个直译器,多个直译器意味着我们尽量减少GIL带来的效能影响。

这是常见web服务的方法,前端的LoadBalancer,大家可能会选择常见的Nginx、apache或者云服务的SLB。

异步IO框架的选择是大家都关心的一个问题。GIL如果是IO密集型,我们用异步能够做到很快。但是它有很适合的应用场景,比如不想做Nginxluv外挂,作为高效能的扩充套件方案,那就用tornado来写,如果内部程式码全是异步的IO操作,它是非常好的,可以组装自己的逻辑,比如积数之类的都可以放在tornado里来做,效能可以得到保障。

另外,PyPy是Python的Just in time 编译器,效能一般要比CPython直译器至少好3倍。但是它和JIT编译器一样有启动慢的特点,所以适合对重启不是很敏感的服务。它的问题是不支援C扩充套件的Python库。

效能瓶颈分析

在现实业务开发中,最主要的是依靠业务日志分析,考虑我们的业务链路中是否存在网络耗时。对一些任务日志可以用AWK或者unit等,去分析出来哪些界面访问量比较多、耗时严重的,使用Cprofile等工具分析问题存在哪里,然后再找到合适的优化方向。

这是一个简单的Cprofile例子,执行def1、def2、def3,去分析一下它的耗时情况。

上面的程式码中有多个函式的执行。可以看到,最后一次的执行耗时是237毫秒。当然,对于profile也可以输出pstat格式的资料,大家能通过视觉化清楚的看到自己函式耗时占比。

优化方法

▌(一)原则

第一,优化时一定要靠资料说话。即使需要牺牲一次迭代去更新一下,也要把资料罗列出来,使之有理有据。我们优化的原则主要有四点:一是用资料说话,资料不只是优化的原因,也是优化的方向,把指标达到一定水准,目的才达到了;第二,不要过早优化或过度优化。否则有可能出现业务偏差;第三,深入理解业务。对产品更加负责;第四,选择好的衡量标准,比如CPU利用率降到多少了。

▌(二)IO密集型

如果是IO密集型的服务,使用多执行绪实际比单执行绪的效能提高很多。但是如果大量IO操作都比较耗时,它的效能未必像想象中那么好。这种情况下建议批量操作,或者改为协程,网络带宽效能会带来很大的提升。此外,减少IO操作也是可行方案。

▌(三)CPU密集型

多执行绪显然已经不适用于CPU密集型的服务,因为频繁的GIL争抢会导致序效能大幅度下降。多程序其实很适合CPU密集型服务。对于CPU密集型的服务,为了减少直译器的损耗 ,最好可以适用C的扩充套件库来提高程式效能,能够一定程度缓解型别转换带来的效能损耗 ,而且可以大幅度提高基础库的执行速度。

▌(四)快取

快取一直是系统性能优化的利器,这对Python是架构性的东西,可能跟语言的相关性没有那么大。但是Python的程式设计方法对快取程式码改造是非常便利的。

这是快取的例子,这个业务逻辑很简单,在现有的生产模型里比较常用。

这是一个有快取的函式,我们在效能调优时需要动态去允许开关函式不快取,必须按照原来的方式执行一遍才能拿到结果。这里有一个计算快取过程,mode是我们开发的模式,可以在函式动态的取mode,达到开关的值。我们可以通过这个开关去让函式得到它执行的方式。

另外,我们在储存序列化资料时最好使用高效能的库,比如cPickle,cPickle虽然比pickle,但是没有cJSON快。可以给储存层、DB层、计算的函式层、应用层都加上快取,但是在Python应用程序之外也有很多架设快取内存的方法。

多层快取虽然是一个架构快取,但是Python开发做扩充套件性应用时,使用者体验是非常好的,简短的程式码开发就可以完成通用功能,而且里面的语言不用动。

▌(五)懒载入

还有一些常用的方法,比如懒载入。这是常用的Lazy单例,呼叫一次之后就不再呼叫了,以后拿到的是初始化好的。

▌(六)一些技巧

对于generator需要谨慎对待。 对于循环遍历,比如遍历10万个资料,generator有可能更慢一些,这种东西是需要分场合的。如果在循环中不需要把所有列表生成出来,那么速度会稍微快一些。

这是一个名称空间问题。第一种状况可能更简单一些,但是它是147毫秒,第二种状况是把循环函式里,快了1倍时间。这是因为Python在执行程式码时遇到了range。对于第一种,Python首先会在本地的变数里找这个range,如果没有找到会去gloabl变数里找range。

对于第二种,range的查询不需要再走gloabl,它走的是load-const,这是一个很快的过程。有些由于空间导致的效能微小的差距,执行少量资料时看不出来,但是大量资料时是非常明显的。

总结

Python这种便利的特性给我们带来很大的开发优势:

资料分析是第一位的,要去优化自己的Python服务。

第二,需要合理的测试环境,不要因为效能调优而影响增加的服务稳定性或者出现故障。

第三,要有的放矢,我们有时面对更多服务拆分或微服务化,对架构说不定有更多好处。比如把IO密集型服务和CPU密集型服务分开做,在前端使用IO密集型的操作。将所有的请求都集中在对外的入口,这样对外服务的效能会得到很大的提高,因为效能压力都分散到各个微服务里了,而同样的效能得到了最大的保障。大家可以多钻研一下,掌握一些技巧。

谢谢大家。

2019-11-07 12:01:00

相关文章