最近处理的性能优化总结思考

按照常理,性能优化应该是属于比较高级,处于项目中后期的工作了,但是如果实现不给力,在项目初期就可以遇到了。

很多人都嫌弃Python慢,个人认为他们之中90%都没有资格这么说,一方面,需要高性能的地方并不是每个项目都需要,另一方面,他们自己写的代码烂的要死,才是罪魁祸首。

Python的代码可读性非常好,利于开发和维护,是对开发者友好的语言。但如果代码写成一团糟,没有扬长避短,导致维护困难,开发新功能无处下手,性能又遇到瓶颈,这个时候又怪罪起Python或是框架来,可以说是愚蠢至极。

我为什么要提上面这段呢?因为我在工作中不断地印证了之前听闻的一个说法。那就是,当一个项目重构后,代码量缩减,性能提升n倍,往往会被人们归功于使用了新语言或是新框架。但这样的理解是不对的,就算是使用同一种语言,同一种框架,在重构时,由于已经有了之前的积累,这些积累包括需求的深刻认识,弊端和bug的提前了解,以此为鉴,才能够在之后的重构时,搭建起较好的架构实现。
如若不然,该挖的坑还会再挖一遍,该跳的坑还得再跳一次。不从以往吸取教训,总结经验,那么永远都不会有出头之日。

最近我就做了一些性能优化工作,也正好是同一语言同一框架的优化。有些东西,虽然我们听说了,记住了,但是如果没有经历过的话,总感觉会缺少些东西。在这次的优化工作中,一些东西不断地从实际当中总结出来,又不断地和以往的知识相互验证,让我感觉受益匪浅,有点融会贯通的感觉,包括上文和下文提到的东西,我都是深有感悟啊!这个时候一定要记录下来,因为感性的认识,和最终能够形成文字表达出来的认识也有不同,后者明显对知识的掌握更深。类比做题容易,但是如果要给别人讲清楚如何做题,那对人的要求就要更高了。
综上啊,这就是经验啊,只有见的多了,才算是身经百战。。。

下面谈谈详细情况。

有些东西是可以立即采用的,比如异步、cache。

异步的作用无疑是非常大的。首页加载慢?运营人员抱怨后台大批量操作时页面卡住?那这个时候就要考虑处理流程里面都必须要用户等在那里吗,用户需要立刻就看到结果吗?在书上,我们经常看到发邮件这种操作会被作者举例,说是非常适合异步。同样,进首页的时候判断用户参与了哪些活动,是否需要发红包,运营审批一些后台数据,这些操作都可以异步实现,对结果没影响,还显著提升页面加载和后台审批速度。
并且,异步处理可以使用多个worker,进一步减少处理时间。
目前我们公司异步处理很复杂,大类可分为Redis队列和Celery处理,其中内部还有细分,我认为这部分能够重构一下,抛弃Redis,统一使用Celery。

cache,译作缓存,也译作快取,实际上这是它的一体两面。什么东西很长时间不会变,什么东西不需要立即对数据库进行写入,这些东西统统可以使用cache。
cache不单单指使用NoSql数据库。其实在代码里面就可以实现某些东西,比如有一个值是从数据库中读取的,但是基本不会改变,那这个时候可以使用@cache_property来将这个值作为类的属性缓存起来。这样的话,当程序启动后(有些场景是第一次访问请求时,比如使用odoo会在第一个访问请求到来时建立url_mapping,会将对应的endipoint存储,endpoint中即是处理对应url的类的实例),就会缓存到内存中(和NoSql同样是内存!),缺点是万一要是值有改变的话,得重启才行。
还有的,当然就是使用NoSql数据库了,我们这边用的是Redis。最近我看了一些《Redis应用实践》,发现其中对Redis的使用非常主动:并不是所有数据都要存放到关系型数据库中,NoSql只是一种辅助,而是将非常多的数据,比如说是涉及到经常改变的数据,直接放到Redis中进行处理,而永久化数据并没有提及。非常遗憾的是,我们公司目前还没有这种做法或是想法。
我们公司已经实现了两套缓存机制,但由于odoo的ORM写入机制,保存到Redis中的数据还没怎么使用呢,就经常被重写,结果导致了非常严重的死锁,所以都废弃掉了。
目前的话我们只是在代码里零星地使用Redis进行缓存,可以说不怎么正常。做优化时,我的代码实现还非常土,同样的语句写了好几遍,被CTO和一位架构师同时吐槽,但目前也没有时间去优雅处理。。。
以前一直没感觉NoSql有多神,直到最近我才深深地理解了什么叫做“保存在内存中”的含义——硬盘就算是SSD,关系型数据库也还是慢,对放在内存里的数据实在是太快了。

以上这两点的话,只要能够理清楚实现逻辑就可以动手做改变了,但接下来的一些优化需要有能够定量的指标才行。

关于Python代码的执行时间,我推荐使用line_profiler,它可以按行标注执行时间,比较好用。其他的很多都是按函数来标注,不太符合我们的需求。
按照line_profiler提供的每行执行时间,以及每行调用次数,可以很轻松的找出瓶颈。无奈的是我司代码瓶颈太多了,我一般是抓大放小,先从最严重的开始。

一些比较好找的点是,ORM的性能,比如odoo中filtered语句就比search语句执行的慢,而search语句中,若使用a.b.c这种格式,那么ORM在翻译search语句至sql语句时,就会变成join语句,这样执行的也慢。再比如,有些功能有更好的实现,比如说是优秀的第三方库,那么对原先的代码进行替换即可。

剩下的都是需要深入场景,深入代码逻辑中去判断是否能够优化。一般而言,如果一行代码执行时间过长,那么就要进行思考,大体上,首先要想想是否需要这么计算,再考虑怎么优化。如果一行代码执行次数过多,那就要考虑是否有多次重复调用了。我在这次优化中,发现有一处调用了300多次,按照场景只需要60次即可,后来发现在代码逻辑中有一处是从上往下进行循环,在循环中调用了一个方法,这个方法又进行从下往上的递归,结果就往返执行了300多次。优化好之后,发现又有一个地方执行了3000多次,我真是百思不得其解啊,后来反复查找,才发现是有一个模型的方法中有一个自动触发的方法,当有些属性改变时就会进行调用。真可谓是一步一坑啊!

说了这么多,其实我们也没有涉及到很高级的技术,都是一些基础。但是,从性能优化的工作中,也能够看出一些问题,也就是所谓的根因:一是Model设计不合理,Model设计的好不好没有标准,是有一些比较教条的规则,但更应该看的是Model的设计是不是非常符合实际的应用场景,我司的设计估计刚开始时还是符合的,但目前已经非常不匹配了。
二是代码质量不过关,我在上文所举的那个例子,就是很好的证明,对于这点我只能说要看个人的自觉了,也没有想到或是听说过有很好的方法,code review这种东西在我司执行效果不佳。
三是极端测试不充分,否则也不会等到线上流量暴增之后,才发现这么多性能问题。
四是性能未量化,当初在写代码时,并没有考虑到哪些地方对性能有要求,只是把功能简单的实现而已。这就好比总分是100分,考了60分已经及格,但是总分突然提升到1000分时,60分就是个垃圾。虽然过早的优化是不对的,但是如果总是低要求的话,那就是挖坑不止,问题永远都解决不完。

上面提到的都是通过技术解决。下面谈谈解决问题的思路,当然是个人见解。实际上遇到问题的时候,我们首要的考虑是,出现问题的场景是真需求还是伪需求,也就是说,问题并不需要都通过技术手段来解决,而且技术手段也不应该是首选。这个过程应该是一步步来,不断地对各种取巧方法进行否定,直到无法可解,才去选择技术来解决。
而当选取技术时,也并不是说直接硬上了,像是采用异步、cache这种讨巧的技术来解决问题其实是非常漂亮的。这次有个需要优化的地方执行了20s左右,原因是会对4000+个浮点数进行加运算,在实际当中,它是一个伪需求,我们通过一个tag来规避它,而通过一个定时任务来执行计算,而计算本身我也只是引入numpy的sum来替换python的内置sum。如果它真的是一个真需求的话,我才可能会去考虑多进程/线程,Map-Reduce,C代码等等其他技术。
这样可能不太符合一个技术人员的定位,面对问题应该正面强上的啊,这样才能证明自己啊……但我恰恰喜欢这种比较“取巧”,“暴力”的方法,而且认为这样才足够优雅。

最后说点其他的,关于死锁。之前我是认为死锁这种高大上的东西得非常大的数据才会出现,没想到啊没想到。归根结底,一部分归咎于ORM的实现,一部分则是代码实现太烂。如果执行够快的话,行级锁是很难触发死锁的。但是按照我司一个事务能执行30min+的尿性,不出死锁才怪。目前我们把一些长事务给分拆,变成一个个小事务对数据库进行commit,来规避死锁的触发。

总之,写代码之前好好理清楚需求,好好设计架构,写代码的时候要对人友好,要写得优雅,写完后要进行测试,要对各种可能出现的情况提前做好判断,是否对性能有要求也要提前想好,并对实现进行验证。

软件工程,软件工程,做工程一定要考虑成本,做工程一定有前有后有各种要求标准,软件工程永远都不是代码罗列。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容

  • Zookeeper用于集群主备切换。 YARN让集群具备更好的扩展性。 Spark没有存储能力。 Spark的Ma...
    Yobhel阅读 7,246评论 0 34
  • Nosql概述 在介绍Redis之前,首先先要介绍Nosql的概念。 互联网架构发展 在90年代的时候,计算机访问...
    COKIDCC阅读 682评论 0 1
  • 本文主要根据美团的技术博客《常见性能优化策略的总结》整理而来。 代码 之所以把代码放到第一位,是因为这一点最容易引...
    你是妖怪吧阅读 4,123评论 0 15
  • 今天看到一位朋友写的mysql笔记总结,觉得写的很详细很用心,这里转载一下,供大家参考下,也希望大家能关注他原文地...
    信仰与初衷阅读 4,725评论 0 30
  • 代码 之所以把代码放到第一位,是因为这一点最容易引起技术人员的忽视。很多技术人员拿到一个性能优化的需求以后,言必称...
    刘臻枫阅读 483评论 0 7