架构之路(二):性能

简介: 我们在上一篇博客中设定了架构的目标,只有一个,就是可维护性。完全没有提性能,这是故意的。   似乎程序员都是急性子,或许是被windows冗长的开机时间折磨够了,有可能是因为提升性能的效果是最显而易见的……总之,我发现,绝大部分程序员对性能的关注和热情是无与伦比的! C#刚刚推出的时候...

 

我们在上一篇博客中设定了架构的目标,只有一个,就是可维护性。完全没有提性能,这是故意的。

 

似乎程序员都是急性子,或许是被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的意思是要取第一个。和性能没关系,如果一定要纠缠性能,那好:你要确定唯一性,当然要做检查(包括不唯一时抛异常),这个性能损失是应该的呀;你要取第一个,当然要进行排序,排序也会有性能损失呀!

 

我刚入行的时候,还很是收藏了几篇文章,比如《高性能编程的十大准则》之类的,里面的内容大致就是,“总是使用StringBuilder,不要使用‘+’;总是使用……,不要使用……”。这类文章下面总是有一堆人叫好,“不错!”,“谢谢分享!”但慢慢的,我就对这些文章产生了怀疑(也应该感谢园子里的老赵,csdn里面的sp1234之类的大神);直到很后来,我才明白为什么这种说法是肤浅的;而只有通过上面的对话,我才能清晰的把我的理解说出来。

 

所有这些牺牲性能的简单封装,都是有其目的的;而其中一个很重要的目的,就是为了提高可读性。你为了性能,故意不使用这些现成的封装,通常,丧失的就是可读性。

 

想当然

 

继续上面这个例子。最开始的时候,这个程序员关于性能的考虑其实是想当然的。这种想当然的情形很多,大致有这几种:

  1. 自己的理解完全就是错的
  2. 自己的理解不能算错,但实际上底层已经对该问题做了优化
  3. 自己的理解没错,底层也没优化

第1、2种比较好理解,第3种为什么也说他“想当然”呢?因为没有和硬件环境相契合。

 

最简单的例子就是“缓存”。比如面试的时候,问你一个问题,“缓存能不能提高性能?”请注意,这是一个陷阱。答案应该是:“不一定”。几乎所有的人都认为,缓存可以迅速改善性能,是因为今天计算机的CPU和磁盘运行速度,远跟不上内存的发展。但即使如此,无节制的缓存,一样可以拖垮整个系统。

 

类似的例子还有很多。你沾沾自喜,我节约了一次磁盘读写的时候,你同时增加了CPU的负荷;你优化了算法,减少了CPU的运算,但其实增加了内存的压力……天下没有免费的午餐。同样的代码,随着数据的增加,硬件的改变,会呈现出截然不同的性能表现。

 

所以,开发过程中,很多的“优化”,其实只是你的想当然。与其这样想当然的优化,不如在拿到性能测试结果之后再有的放矢的进行优化。这时候,又回到了我们之前说的,是不是代码的可读性更重要?这样你才能迅速的找到该优化的瓶颈啊!否则,一堆乱七八糟看都看不懂的代码,你怎么去优化,你连该优化的点都找不到。

 

难以维护

 

另一个搞笑的例子是关于我自己的。创业家园项目里有一个功能:显示博客正文的同时提供一个上一页下一页的链接。惯常的做法就是直接在数据库里查就是了,但我总觉得不对,这样做两次查询有必要么?能不能优化?于是我想到了一个“绝妙”的点子:为什么不直接在博客里存储上一篇和下一篇的Id呢?这样我一次性数据往返就能取到所有数据了嘛!各位同学是不是觉得我这个主意很棒?

 

噩梦由此开始了。

 

首先,我们是想在发布博客的时候,设置他的上一篇和下一篇。但是,上一篇好设置,下一篇呢?还没有啊!怎么弄,就只好在博客发布的时候,设置他的前一篇,同时设置他前一篇的后一篇。

然后,我们新添加了一个功能,除了上一篇下一篇以外,还需要在当前博客所在分类中的上一篇和下一篇。怎么办?再加字段呗。所以,博客里就有了Previous, PreviousInCategory, Next, NextInCategory。这时候,就感觉到有点不妥,但还可以接受。

接着,出现了一个问题,上一篇下一篇博客被删除了,怎么办?这个过程,就相当于从一个双向链表里移出一个节点一样麻烦。头开始有点大了。

再接着,博客除了发布删除以外,还有各种其他状态,比如被屏蔽。而且被屏蔽之后,能否显示和当前用户又有关系。当前用户是普通用户,不能阅读;当前用户是作者自己,就能够阅读。怎么办?首先,屏蔽的时候,要设置上一篇下一篇;屏蔽取消的时候,还是要设置上一篇下一篇。然后,上一篇下一篇得根据当前用户不同变化的这个问题,基本上就傻眼了……

 

最后流着泪把辛辛苦苦折腾了好久的代码全改回来,就通过数据库查呗,多么清晰简洁的逻辑啊!性能问题?首先,这样做造成了性能问题么?然后,就算有问题,用一个缓存能解决不?

 

合理浪费堆硬件

 

说了这么多,不知道有没有引起同学们的反思。可能大家还是过不去心里那道坎:明明有一种性能更高的方法我们为什么不用?

 

因为浪费呗!

 

什么?你有没有搞错?我的代码,至少省了一块内存条!那是你还没从“穷学生”的角色里转换过来。你花一周的时间对代码进行了优化(就先不考虑你的优化带来的维护成本增加了),为老板省下了一块内存条的钱。你以为老板会拍着你的肩膀表扬你么?老板打不死你!

 

兄弟,账不是你那样算的。你是学生的时候,你的时间成本是0;但你进入工作岗位,每一天都是要发工资的。

 

通过代码来调高性能,是一种无奈——对硬件性能不够的妥协(参考:80年代游戏开发者的辛苦困境。这样写性能就高,但为什么现在没有谁再这么写代码了?)。否则,绝大多数情况下,堆硬件比优化代码的效果好得多,而且便宜得多。硬件的成本按摩尔定律往下降,我们程序员的工资也能按摩尔定律减么?

 

明明window 10 比window 95更耗性能,为什么今天没人用window 95?为什么VS 2013要10G的空间我们都还屁颠屁颠的赶紧装上?为什么现在大家都用C#,没人用汇编?我们站在人类文明积累的今天,就应该理所当然的享受这一切成果。有打火机你不用,你要钻木取火。如果你是因为要学贝爷荒野求生装逼,可以理解;如果你说你是因为怕浪费天然气,我……我……我怎么说你呢?“给做打火机的一条活路,行不?”同样的,程序员大神同学,你就当做好事,给下面写底层做硬件的一条活路吧!你的代码都是010001000010000001010101……了,你让其他人怎么活啊?

 

最后,我突然想到的一个程序员为什么对性能如此敏感疯狂,对可维护性毫不在意的一个可能原因:

  • 性能很好理解,卡得要死和跑得飞快;可维护性很不好理解,至少得跑个两三年才能体现,那时候,谁知道爷在哪里偷着乐呢
  • 性能上不来,程序员只有羞愧的低着头,都是我的错;需求有变更,开口就骂,“哪个SB又要改……”;

大家觉得是不是这样的?所以,愿意把代码百炼成钢绕指柔的人少。想来,是一种莫名的悲哀和凄凉。

 

最后最后,有一些我能想到的名言警句供大家参详:

  • 过早的优化是万恶之源
  • 优化首先需要找到性能“瓶颈”。否则,任何人都可以随手一指,“这段代码需要优化”。
  • 可读性更强的代码总是更好优化
  • 硬件永远比软件便宜

 

忘了说我的项目了。目前主要集中在创业家园项目的开发上,正试图从svn转成git源代码控制。不太懂Git,说起来都是泪,懂的同学帮帮忙吧!

相关文章
|
3月前
|
存储 缓存 Cloud Native
MPP架构数据仓库使用问题之ADB PG云原生版本的扩缩容性能怎么样
MPP架构数据仓库使用问题之ADB PG云原生版本的扩缩容性能怎么样
MPP架构数据仓库使用问题之ADB PG云原生版本的扩缩容性能怎么样
|
6月前
|
缓存 NoSQL 应用服务中间件
性能基础之大型网站架构演化(整理篇)
【2月更文挑战第14天】性能基础之大型网站架构演化(整理篇)
94 1
性能基础之大型网站架构演化(整理篇)
|
12月前
|
算法 编译器
【计算机架构】响应时间和吞吐量 | 相对性能 | 计算 CPU 时间 | 指令技术与 CPI | T=CC/CR, CC=IC*CPI
【计算机架构】响应时间和吞吐量 | 相对性能 | 计算 CPU 时间 | 指令技术与 CPI | T=CC/CR, CC=IC*CPI
1084 1
|
2月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
335 37
|
28天前
|
安全 数据安全/隐私保护 UED
优化用户体验:前后端分离架构下Python WebSocket实时通信的性能考量
在当今互联网技术的迅猛发展中,前后端分离架构已然成为主流趋势,它不仅提升了开发效率,也优化了用户体验。然而,在这种架构模式下,如何实现高效的实时通信,特别是利用WebSocket协议,成为了提升用户体验的关键。本文将探讨在前后端分离架构中,使用Python进行WebSocket实时通信时的性能考量,以及与传统轮询方式的比较。
55 2
|
2月前
|
缓存 运维 NoSQL
二级缓存架构极致提升系统性能
本文详细阐述了如何通过二级缓存架构设计提升高并发下的系统性能。
112 12
|
2月前
|
缓存 安全 Java
如何利用Go语言提升微服务架构的性能
在当今的软件开发中,微服务架构逐渐成为主流选择,它通过将应用程序拆分为多个小服务来提升灵活性和可维护性。然而,如何确保这些微服务高效且稳定地运行是一个关键问题。Go语言,以其高效的并发处理能力和简洁的语法,成为解决这一问题的理想工具。本文将探讨如何通过Go语言优化微服务架构的性能,包括高效的并发编程、内存管理技巧以及如何利用Go生态系统中的工具来提升服务的响应速度和资源利用率。
|
3月前
|
缓存 前端开发 算法
Fiber 架构如何提高性能和响应性的
【8月更文挑战第6天】Fiber 架构如何提高性能和响应性的
44 1
|
3月前
|
消息中间件 缓存 Java
如何优化大型Java后端系统的性能:从代码到架构
当面对大型Java后端系统时,性能优化不仅仅是简单地提高代码效率或硬件资源的投入,而是涉及到多层次的技术策略。本篇文章将从代码层面的优化到系统架构的调整,详细探讨如何通过多种方式来提升Java后端系统的性能。通过对常见问题的深入分析和实际案例的分享,我们将探索有效的性能优化策略,帮助开发者构建更高效、更可靠的后端系统。
|
4月前
|
安全 数据安全/隐私保护 UED
优化用户体验:前后端分离架构下Python WebSocket实时通信的性能考量
【7月更文挑战第17天】前后端分离趋势下,WebSocket成为实时通信的关键,Python有`websockets`等库支持WebSocket服务。与HTTP轮询相比,WebSocket减少延迟,提高响应。连接管理、消息传输效率、并发处理及安全性是性能考量重点。使用WebSocket能优化用户体验,尤其适合社交、游戏等实时场景。开发应考虑场景需求,充分利用WebSocket优势。
136 3

热门文章

最新文章