一个大型的网站,不仅前台的优化要做的好,后台的逻辑算法也要考虑到多种情况,当一个网站访问量过大的时候,优化在这里就显得很重要了。
似乎程序员都是急性子,或许是被windows冗长的开机时间折磨够了,有可能是因为提升性能的效果是最显而易见的……总之,我发现,绝大部分程序员对性能的关注和热情是无与伦比的!
C#刚刚推出的时候,就有人摇头晃脑的说,“嗯,自动垃圾回收,性能不行吧?”
DataSet横空出世,马上有很多人写代码,在DataSet里插入几百万条数据,证明DataSet的性能问题
Linq当然更要被骂了,尼玛用反射?反射是什么,同学们知道么?性能大老虎呀!更不用说那些自动生成的sql了,有我手写的高效么?
……
所以直到今天,我仍然看到很多程序员无怨无悔的用存储过程来构建他们的系统,一个存储过程可以有几千行!然后,他们很无辜的问,“业务层有什么用?究竟能干些什么呢?”
在带团队的时候,我最怕讲的就是性能有关的问题。你要是不谈性能呢,那代码有时候真心看不下去;你要是强调性能呢,不知道他会给你整出什么幺蛾子出来。其实这就是一个“度”的掌握,所以非常难以用语言予以表示清楚。所以无数次挫败之后,我只好咬牙切齿的说,“你的代码,只有一个评判标准,可维护性。性能的问题先不管!”这个答案似乎并不能服众——尤其是对有上进心的程序员而言。
所以,我先专篇讲性能,希望能帮助大家更清楚的认识这个问题。
一、性能不是不重要,而是他没有可维护性重要。要理解这一点,首先要理解可维护性的重要(请再读上一篇我花数周找bug的段子);然后要明白:解决性能问题,我们可以有很多代码以外行之有效的方法,而可维护性基本上就只能靠代码了;最后,还是要牢记:没有牺牲,就没有胜利!
二、所以,在绝大多数情况下,当性能和可维护性相冲突的时候,性能让位于可维护性。我们采用其他办法来弥补代码性能不够高的问题。
空洞的说教没有意义。我们还是举例来说明吧!
前段时间我review代码的时候发现,这个程序员用Linq之后老是用First()而不是Single(),我就奇怪了,按业务逻辑,返回的值就应该是一个,难道可能会是多个,多个应报异常,不应该取First()就完事了呀?想了一会儿,问这个程序员,他的回答让我瞬间一种无力感,“First()性能更高呀!”以下为对话实录:
“你怎么知道First()性能更高呢?”我问。
“First()嘛,取了第一个合格的值就返回,就不会继续查下去了;Single()的话,就会一直查,查出所有数据,然后再取其中的一个。”
“你确定?你知道有一种东西叫做索引不?”
“啊?……”
然后我简单的告诉他,索引是一种树状结构,可以让查询更快等等。
“但我还是觉得应该用First()”,他想了一会儿,还是很坚定。
“为什么?”,我不明白了。
“就算有索引加快了查询速度,但用First()在加快了速度上更快呀!更快总是没错的吧?”
“……”,我真不知道该怎么说了,最后突然灵光一闪,“好吧,那你说说,微软为什么要搞一个Single()方法出来呢?就为了搞出来误导你们?让用First()的产生优越感,嘲笑用Single()的?”
他陷入了沉思。
评论里还在纠结Single()/First()的同学,请大声的吼三遍:可读性!可读性!!可读性!!!
发现同学们还在纠结这个细节。好吧,再解释一下:
1、你怎么知道数据库用的就是MSSQL呢?你怎么知道就是用的关系数据库呢?NoSQL不行么?所以,你怎么就知道Single()/First()具体是怎么执行的呢?比如我就要写个Linq实现,把所有的数据全取出来,然后再在内存里排序,最后取First呢?
2、这里我们考虑可读性,意思是:读代码时,看到Single()就能瞬间知道coder的意思是取唯一的一个;看到First()就知道coder的意思是要取第一个。和性能没关系,如果一定要纠缠性能,那好:你要确定唯一性,当然要做检查(包括不唯一时抛异常),这个性能损失是应该的呀;你要取第一个,当然要进行排序,排序也会有性能损失呀!
所有这些牺牲性能的简单封装,都是有其目的的;而其中一个很重要的目的,就是为了提高可读性。你为了性能,故意不使用这些现成的封装,通常,丧失的就是可读性。
继续上面这个例子。最开始的时候,这个程序员关于性能的考虑其实是想当然的。这种想当然的情形很多,大致有这几种:
1.自己的理解完全就是错的
2.自己的理解不能算错,但实际上底层已经对该问题做了优化
3.自己的理解没错,底层也没优化
第1、2种比较好理解,第3种为什么也说他“想当然”呢?因为没有和硬件环境相契合。
最简单的例子就是“缓存”。比如面试的时候,问你一个问题,“缓存能不能提高性能?”请注意,这是一个陷阱。答案应该是:“不一定”。几乎所有的人都认为,缓存可以迅速改善性能,是因为今天计算机的CPU和磁盘运行速度,远跟不上内存的发展。但即使如此,无节制的缓存,一样可以拖垮整个系统。
类似的例子还有很多。你沾沾自喜,我节约了一次磁盘读写的时候,你同时增加了CPU的负荷;你优化了算法,减少了CPU的运算,但其实增加了内存的压力……天下没有免费的午餐。同样的代码,随着数据的增加,硬件的改变,会呈现出截然不同的性能表现。
所以,开发过程中,很多的“优化”,其实只是你的想当然。与其这样想当然的优化,不如在拿到性能测试结果之后再有的放矢的进行优化。这时候,又回到了我们之前说的,是不是代码的可读性更重要?这样你才能迅速的找到该优化的瓶颈啊!否则,一堆乱七八糟看都看不懂的代码,你怎么去优化,你连该优化的点都找不到。
另一个搞笑的例子是关于我自己的。创业家园项目里有一个功能:显示博客正文的同时提供一个上一页下一页的链接。惯常的做法就是直接在数据库里查就是了,但我总觉得不对,这样做两次查询有必要么?能不能优化?于是我想到了一个“绝妙”的点子:为什么不直接在博客里存储上一篇和下一篇的Id呢?这样我一次性数据往返就能取到所有数据了嘛!各位同学是不是觉得我这个主意很棒?
首先,我们是想在发布博客的时候,设置他的上一篇和下一篇。但是,上一篇好设置,下一篇呢?还没有啊!怎么弄,就只好在博客发布的时候,设置他的前一篇,同时设置他前一篇的后一篇。
然后,我们新添加了一个功能,除了上一篇下一篇以外,还需要在当前博客所在分类中的上一篇和下一篇。怎么办?再加字段呗。所以,博客里就有了Previous, PreviousInCategory, Next, NextInCategory。这时候,就感觉到有点不妥,但还可以接受。
接着,出现了一个问题,上一篇下一篇博客被删除了,怎么办?这个过程,就相当于从一个双向链表里移出一个节点一样麻烦。头开始有点大了。
再接着,博客除了发布删除以外,还有各种其他状态,比如被屏蔽。而且被屏蔽之后,能否显示和当前用户又有关系。当前用户是普通用户,不能阅读;当前用户是作者自己,就能够阅读。怎么办?首先,屏蔽的时候,要设置上一篇下一篇;屏蔽取消的时候,还是要设置上一篇下一篇。然后,上一篇下一篇得根据当前用户不同变化的这个问题,基本上就傻眼了……
最后流着泪把辛辛苦苦折腾了好久的代码全改回来,就通过数据库查呗,多么清晰简洁的逻辑啊!性能问题?首先,这样做造成了性能问题么?然后,就算有问题,用一个缓存能解决不?
明明window 10 比window 95更耗性能,为什么今天没人用window 95?为什么VS 2013要10G的空间我们都还屁颠屁颠的赶紧装上?为什么现在大家都用C#,没人用汇编?我们站在人类文明积累的今天,就应该理所当然的享受这一切成果。有打火机你不用,你要钻木取火。如果你是因为要学贝爷荒野求生装逼,可以理解;如果你说你是因为怕浪费天然气,我……我……我怎么说你呢?“给做打火机的一条活路,行不?”同样的,程序员大神同学,你就当做好事,给下面写底层做硬件的一条活路吧!你的代码都是010001000010000001010101……了,你让其他人怎么活啊?
最后最后,有一些我能想到的名言警句供大家参详: