因为大多数项目并没有把不卡顿当作目标。
你可以这样理解:不卡顿是一个功能,如果这个功能在开发项目计划与进度中没有列为目标,那么这个功能就不会被投入力量去完成。测试也不会将卡顿报告为bug,开发也接不到优化卡顿的任务单。
对于某些项目,明确把不卡顿作为开发目标的,自然就还可以优化。虽然这代价也不小,但大多数程序多多少少是有优化空间的。只不过,通常都没有必要优化罢了。
高票说的很棒了,我来提供另一个视角吧。
现在为了便宜、方便,绝大部分程序是用脚本语言写的。
这是因为脚本语言入门简单,很容易就能雇到程序员;而公认高效的编译型语言如C、C++、Delphi,或者即地编译的Java等专业语言入门门槛很高——起码知乎上已经有很多人持续好多年的希望它们灭绝了。以至于一旦我提到C,经常就有一票人在我的评论区酸溜溜的对以汇编……
但是,python/php/js之类语言的性能是非常糟糕的。比如这个就是一个很不全面的测试:
来自: Python与C语言、Java、Nodejs、Golang进行性能测试比较 - 云+社区 - 腾讯云 (tencent.com)
可以看出,在查找质数这个测试场景下,C/Java的执行效率是python的20/17倍。
这是个什么概念呢?
现代CPU的主频大约是4~5G,它的1/20是250MHZ。
换句话说,不考虑多核因素,只算单核性能的话——你以为你在用最先进的i9?
错。你不过是在用老掉牙的、1997年上市的奔腾2代罢了。
24年前的老爷车能跑出这个效果,知足吧。
当然,奔腾2也很强了。对个人用户的常规任务来说,它完全是性能溢出。这就是python、js大行其道的根本原因——对你来说,它足够用了。
但,这也正是C/C++、Java必须继续存在的根本原因——在它们的帮助下,才能让python把i9当奔腾Ⅱ 266MHZ用,帮你处理点不需要性能的日常任务。一旦需要性能……
尤其是,为了方便、易控制,现在恨不得一切都用h5跑在浏览器里。
注意这里并不是说脚本语言是程序卡的根本原因;事实上,电脑上所有性能敏感的地方,跑的都是C/C++或者Java(甚至,由于GC造成的stop world问题,很多地方Java都是不能用的);现代的高分辨率高帧率图像的确也需要非常非常多的性能。
这里说的是:类似脚本语言替代编译型语言这样,为了方便程序员、降低开发费用而作的“负优化”特别特别的多,多到已经规模化、系统化、标准化的程度了。
此外,国内厂商,为了垄断或者别的什么原因,致力于负优化或者阉割你的系统……
举例来说,Android有个webview组件。这个东西在Android 4.4之前基于webkit,之后就是chrome内核了——这玩意儿对H5的支持还是很不错的。
但是,国内手机厂商不约而同的阉割了这个组件,故意制造不兼容。
结果就是,除非你的app自带浏览器内核,否则就别想正常运行。
为什么我说他们是故意呢?
因为最近调一个程序,丢浏览器能跑,放手机上不行;再一试手机自带浏览器,也不行——然而Google play装的chrome又行了。
于是决定看看究竟哪的问题,打开http://html5test.com,手机自带浏览器得分还挺高;尤其其中我们程序功能相关的特性居然也有。
那怎么不执行我们的程序呢?想了想,我尝试重新打开过去跑不过的那个应用。顺利打开。
明白了,发现你跑html5test,它就临时打开开关,让你看它特性支持的足足的;不跑,那就不给你支持。
于是继续查,看看webview能跑多少分——果然,webview得分极低,把本来支持的近80项html5特性都关掉了。
这就使得这些阉割版系统的webview组件压根没法用。除非自带浏览器内核,绕开这个玩意儿。
举例来说,国内某龙头企业的即时通讯软件,它的国内版就不得不带了个蹩脚的浏览器组件。不然很多功能就没法实现。
但这个组件使得软件变得更加臃肿;且优化水平很差,使得这玩意儿用起来总是能明显感觉到卡卡的——正常的js引擎跑Javascript,如果还能有C的性能1/20的话,这个组件可能就只能到1/23或者1/25,甚至更差。
如果你用的是海外版或者国外品牌的手机,那么可以通过控制台卸载这个组件,让它用标准的webview,流畅度就会立竿见影的提升;但如果你用的是国内版的手机……卸载这个组件只会让它崩溃、或者页面故障。
类似的,你直接从Google play装的这个软件,它就原生不带这个组件——后果嘛,那就是流畅多了。
当然,前提是你不能用国产阉割版手机系统。卡点总比用不了强。
类似的,知乎手机版也自带了一个很烂的浏览器。chromium 87内核,html5test只有469分。但这个玩意儿比起手机自带的破烂油腻UI浏览器的444分还是高了不少。
可想而知,如果不浪费你的空间、使用阉割更严重的手机自带webview的话,体验会烂到什么地步、开发测试又会不可控到什么地步。
总之,在这些“负优化”的共同努力下,你的电脑经常要发着i9的热,享用着奔腾Ⅱ的性能……如此一来,你不卡,谁卡?
2000年的时候,做一个PS2的儿童组合游戏,画面肯定是儿童风格的,用到的图片非常多,造成占用空间巨大。
怎么办?压缩呗。搬来几种现成的压缩轮子,JPG压缩,ARA压缩,ZIP压缩……,效果明显,压缩比十几倍以上吧,但是占用空间还是比较大,光图片都上百M,现在看100M简直是洒洒水,小意思。但在当时算是巨无霸了。(SONY先后来了两个大牛,无功而返)。
我每天对着图片库也是焦头乱额,被逼无奈只有自己来想怎么压缩,发现这些儿童风格的图片有一个共性,就是都有成片的RGB值相同的色块。然后花了一个晚上,敲了个简易的压缩程序,仅仅几百行代码而已。算法很简单,就是找到色块边缘,记录下来,并记录分块后每块的RGB值,解压过程相反,相当于先把边缘描出来,然后再填颜色。测试后效果惊人,绝大部分图片压缩比在200以上。
很惊喜,电脑上花几天时间测试完善。
然后上PS2模拟,结果发现偶尔会卡,分析比较后,就是解压缩太耗时,然后对算法优化优化优化,折腾半个多月,基本白干。算法的小细节优化,根本省不了多少时间。
后来只有搞新的算法,简化压缩流程,不寻找图片边缘了,把整个图片看成一个像素流,遇到像素RGB 变化的,记录位置,后边连续相同像素的数量和RGB值,简单暴力,比第一个算法容易太多了,结果压缩比基本上接近100,还算不错。
这次先在电脑上测解压速度,跟之前的自研算法和JPG等解压快的都不在一个数量级,解压时间基本可以忽略。
啰嗦这个事,好像跟题主问题无关。但事实上这个事说明了:
软件开发者永远在省事和能用的边缘不停试探。
软件开发者永远在省事和能用的边缘不停试探。
软件开发者永远在省事和能用的边缘不停试探。
所以,不管硬件的速度在未来快到何种程度,永远都会有程序比较卡。
假设某一天,硬件速度到了极限,无法提升了,反而会发现你常用的软件都不卡了,这是什么鬼逻辑?自己好好想。
因为写高效率的程序不挣钱。
在计算能力匮乏的年代,银行和一些政府机构的业务程序是用cobol写的。
一些冷门业务,譬如缴费之类的业务。你交钱,录入记账,打印回单,定期打印报表,你可以查询记账,再有一些用户管理,系统管理功能的程序。
一台性能相当于iPhone4s性能的服务器,就可以负载一个城市的业务,带一个营业大厅的几十台终端。
整套系统,一个小U盘就可以带走。
后来,同样的业务功能,在windows下开发,用微软这套东西,有图形界面,就需要比较强大的服务器了。
现在,程序员开发一个人缴费的APP,占用的储存空间和内存已经比当年管理一个城市缴费的系统更大了。
现在的程序员能不能用高效率的工具写高效率的程序呢?
当然能。
在PC程序膨胀的时候,功能手机也需要写程序,当时手机硬件羸弱。
于是我们看到了诺基亚上只有几十K的QQ,已经有完整的核心功能。
后来智能手机出现,手机性能上去了,手机APP就开始放飞自我了。
早期,安卓手机不流畅,厂商还做一些优化。安卓下WPS的6.2版本,还能秒开。
后来手机硬件上去了,厂商给APP加功能,窃取隐私。程序员不做性能优化,时间长了,优化的经验也没了。即使要写高效的程序也很难了,况且老板也不要求效率,先跑起来再说。
结果就是现在的样子。国外开发APP能好一点。
结果,用户的手机,很快就被臃肿的APP拖垮了,不得不换新手机。
而手机APP很多是不升级不让你用,逼迫客户升级无用的功能。这样用户不得不换。
相比之下电脑好很多。
电脑的程序不依赖升级和网络就能用,我办公打字,只要求所见即所得。
用winxp加office2003,能一直用到硬件报废为止。所以,我们现在能看到一些地方用十几年,二十年历史的电脑在工作。
软件低效的问题,只能政府管。
政府不允许低效软件上线。规定低配置手机跑流畅的APP才能审批上应用市场。
程序员和老板自然就要求优化了。
因为在等待外设的响应。
比方说,CPU从磁盘中读取一个文件。虽然现代CPU每秒数亿次计算,但是磁盘每秒钟却读不出来数亿M的数据。对不起,请CPU等一下。
比方说,我在上海从我的电脑发一个文件给北京的电脑。虽然现代CPU每秒数亿次计算,但是网络却没有这么快。
即使是执行一个简单的计算程序,因为现在的操作系统是支持同时执行多个程序的。因此上需要另外一个之前执行的程序使用的内存中,对于前一个程序有用的信息先放到磁盘上,之后才能跑你的程序。专业术语叫磁盘交换。
至于手机上的APP。这个是JAVA虚拟机的锅。你的每一个操作都是先转换成JAVA虚拟机指令,由虚拟机指令转换成CPU指令执行。
之前PC界有个Wintel联盟:微软和英特尔。
英特尔负责设计更快的CPU,微软负责把CPU升级的部分用完。争取让用户每3-5年升级一下电脑。
后来不仅仅是CPU,显卡、内存、总线的升级,都要占用。以至于对于部分(如果不是大部分)用户来说,隔几年升级不过是为了让电脑在新的系统下,看起来不是那么慢而已。
这是一种阳谋。
如果一台计算机(现在的智能手机也是计算机)买过来就能用10年甚至更长时间,估计硬件公司和软件公司就快饿死了。
这和历史上电灯泡生产厂家,故意把电灯泡的寿命从10年缩短为3年没有区别(后来缩短成一年?)。
在这个商业模式里,最优秀的程序员也只是帮凶而已。
而那些不那么优秀的程序员,则变成了被嘲讽的对象。
愚蠢的程序员,则成了这个话题的躺枪者。
比如,到现在竟然还有在两个只有不到1000条数据表进行联合查询,就会导致界面失去响应的大侠。
好几个原因
第一个原因,他们没做硬件加速渲染,也就是对于图像的处理,需要交给gpu完成,光cpu很难达到理想的效果,这个在做动画的时候,眼睛会比较明显能感觉得到
这个比较典型的例子就是java的swing,缺省是不会做硬件加速渲染的,除非你手动打开opengl,而且如果你的机器上不支持opengl的话,那么用的是java2d,也就是纯软件渲染,会很明显滴感觉到卡顿
值得说明的是,这不是说你换c什么就能解决的,如果你用c也是纯软件渲染的话,一样慢
所以一般都是对opengl,vulkan,metal等硬件加速渲染的渲染管道api做封装,然后再将其包装成对应语言的api,然后通过调用这些api,来调用硬件加速渲染的管道以处理图像
cpu的优势是整形计算,gpu都是浮点型运算嘛,图像其实都是浮点型计算,cpu的强大算力对于图像处理帮助有限
第二个原因,没有做aot,没有编译成native,机器码,所以程序在执行的时候,经常会经过几个步骤,就先将源代码或者字节码之类的,编译成机器码,然后再执行,这样做的好处就是方便你跨平台,因为你编译成机器码之后,生成的这个机器码是没有办法直接交给其他平台去执行的
只能是能够执行该机器码的平台才能执行,比如你针对windows编译了windows可以执行的机器码,那么该机器码拿到linux上去,就无法执行了
但是如果你生成的不是机器码,而是字节码,甚至源代码,那么你就可以在不同的平台上执行
把源代码编译成机器码,这个现在叫做aot,如果不编译成机器码,以能够跨平台的一种命令形式存在的话,这个叫做jit,just in time compilation,aot和jit的对比,各有优劣,但是简单说,就是aot的产物,启动更快,内存使用更少,这个对于一个客户端程序而言,启动快慢会让你有一个直观的感受,如果启动快,你可能就会觉得快了,虽然执行一段时间之后,jit并不会比aot慢,甚至会更快一丢丢,但是第一影响一旦形成,后续你就比较难以扭转,哪怕后面jit可能会更快一点,但是你也未必会有这种感觉
那现在很多程序,是没有做aot的,尤其是web上用所谓的跨端技术搞出来的东西,很多都是脚本套个浏览器就到处用,这样做能不慢么?
不过好的地方就是,现在谷歌,java,苹果,都在做aot,纷纷推出aot技术,像苹果,会对你上架app store的app要求编译成native,google play也会在你下载的时候,对app做native编译,所以从目前大趋势看,客户端还是越来越多要求做aot编译的,至少你在发布时候,一般都会做aot,能做的话,都会做aot,这个趋势应该很明显,做了aot之后,客户用的程序多少都能感觉到快一点
第三个原因可能是gc带来的,gc要想写好,并不容易,真正能把gc做好的技术并不多,像swift,依旧没有特别省心的gc技术,要用arc,一定程度上增加了程序员的心智负担,实测发现,几年前,市场上常规的gc,都还在100ms上下,现在经过各种优化,尤其是1ms以内的zgc正式下发生产之后,我们有理由相信,以后gc带来的卡顿问题会越来越少
第四个,额外操作,有些程序,并不是你表面上看上去的那么简单,不多解释,你懂的
第五个,代码写得比较糟糕,但是跟前面几个比起来,我觉得一般程序员代码写得糟糕带来的影响,可能没有前面几个造成的影响大,当然不可否认,如果代码写得不好,也有可能造成卡顿的后果
因为现在的程序员图省事懒得优化了,反正「CPU 很快」。
计算机产业中有个很著名的安迪-比尔定律,重要性不在摩尔定律之下。
大家都知道摩尔定律:硬件性能每18个月就会翻一番。虽然随着CPU制程接近极限,最近几年摩尔定律基本失效了,但是硬件性能的上涨依然很快。现在的CPU比起四五年前的CPU肯定是快了一倍以上,但是我们并没有感觉到软件运行也快一倍。
安迪-比尔定律是这么说的:安迪给予的,比尔都会拿走。安迪是Intel的前CEO,而比尔是微软的前CEO。这条定律的意思是说硬件增加的性能都会很快的被软件消耗掉。基本上,最新的软件总是会尽可能的榨干硬件能提供的全部性能资源。
其实,软件开发过程中免不了各种“优化”,就是对想要干的事情进行一些简化,以保证硬件性能能够匹配。在Windows系统出现之前,软件只通过简单的文字来展示界面,后来出现了图形界面、高清图标。显示器分辨率从640*480发展到3840*2160,所需的系统资源是指数级提升的。想要模拟的东西也越来越接近真实世界。
其次,软件开发中有一些原本由程序员人工确定的部分,现在也逐渐变成由软件自动实现。比如在前端开发中,以前我们要逐一确定界面上发生修改的元素,并用代码逐一将这些元素更新。现在我们只需提交更新后的界面描述,软件自己将会比对新界面和老界面之间的区别,然后将产生变化的元素更新。这极大的简化了程序员的开发工作,但是却也消耗了一些性能。
虽然现在硬件发展的很厉害了,但是软件想要的野心更大,现在无法由计算机实现,仍然需要简化的部分在软件开发中还有许多。虽然摩尔定律基本失效了,但是安迪-比尔定律应该会继续有效下去很长时间。
这不关 CPU 的事。
如果一个应用程序打开慢,在硬件条件还可以的情况下,那么大概率是这个应用程序里藏着“shi山”一般的代码,让 CPU 做了非常多的无用功,就好像备胎再怎么努力怎么舔女神,终究都不会有好果汁吃。
我之前就看过这么一个有趣事情。
GTA 5联机版这款游戏都发行了长达 7 年之久,但是还是打开那么慢,进游戏少则等5、6分钟,多则20分钟,引发了无数玩家吐槽抱怨……
终于有一天,一个黑客大哥实在忍不了,用「逆编译器」逐条查看运行情况,终于找到原因。
原来,R星(游戏开发商RockStar)写的代码太低效,加载时,一个if语句竟然循环了19.8亿次…,也就是说推卸了一堆 shi 一样的代码,能不慢才怪!
贾浩楠 发自 凹非寺
量子位 报道 | 公众号 QbitAI
参考资料:量子位:GTA5要跑19.8亿次if!黑客修改后直接省70%加载时间
加载GTA 5 Online到底有多慢?
△硬件拉满的土豪玩家请无视
Reddit相关板块发起的调查中,超过80%的玩家,都要等3分钟以上,有的甚至超过15分钟。
而且,从7年前Online上线到今天,这个情况丝毫没有改善。
暴躁的,已经骂起了脏话……
但奇怪的是,如果你选择是故事模式(单机版),加载就会快很多,感觉甚至像两个不同的工作室开发的游戏。
具体到这位黑客大哥的例子,他自己的硬件配置如下:
CPU,是老而弥坚的AMD FX-8350,2012年上市,采用“推土机”架构,超频潜力惊人。
显卡还是GTX 1070。
这样今天看起来老旧的配置,打开单机版GTA 5需要1分10秒,而加载联机版则6分钟起。
黑客大哥用了最简单的Windows任务管理器,来判断联机版GTA 5在启动时,都调用了哪些计算机资源。
在1分钟的时间分界线上,之前是加载的是单机和联机版通用的基础内容,之后是联机版独有的内容。
可以看到,联机版GTA 5,加载时调用大量CPU资源至少长达4分钟之久。
而同时,内存、GPU、硬盘的使用情况几乎没有明显变化。
所以,问题大概率出在代码上。
插个题外话。
小林在知乎写了很多图解网络和操作系统的系列文章,期间收获到很多知乎朋友的认可和支持,正好最近图解网络和操作系统的文章连载的有 20+ 篇了,也算有个体系了。
所以为了方便知乎的朋友们阅读,小林把自己原创的图解网络和图解操作系统整理成了 PDF,一整理后,没想到每个图解都输出了 15 万字 + 500 张图,质量也是杠杠的,有很多朋友特地私信我,看了我的图解拿到了大厂的offer。
现在图解网络和图解系统 PDF 开源给大家:
好了,继续说 GTA 游戏的事情。
黑客大哥在开扒R星代码之前,就说:
我闻到一股烂代码的味道…..
为了找出到底那一部分程序卡住了CPU,他使用了工具Luke Stackwalker,对CPU任务堆栈进行采样分析。
Luke Stackwalker对于闭源应用程序,可以转存正在运行的进程堆栈,和当前指令指针的位置,以一定时间间隔建立一个调用树。
最后将数据整合,就可以得到程序运行统计数据。
从结果上看,一共有两个函数“卡住”了CPU:
于是他使用专业的代码拆解工具,给GTA 5来了一个“开膛破肚”。
沿着调用栈往下走,发现问题出在一个sscanf函数上。
sscanf的功能是读取格式化的字符串中的数据,而在GTA 5中,它正在读取的是一个10M左右,有63000多个条目的JSON文件。
这个文件到底是干什么用的?黑客大哥推测,这可能是游戏内购商店的相关内容。
在具体运行时,sscanf对于每个有效值,逐个读取每一个字符,然后返回结果,之后指针移向下一个值,循环往复……直到把10M文件全部扫一遍。
再看第二个问题,这是一个存储命令,对象是item,具体是什么不得而知。
但是保存前,有一个if语句,逐一比较item内项目的哈希值,检查它们是否出现在某一列表中。
按照他的计算,这一步if,要执行(63000^2+63000)/2 = 1984531500次!
没错,等待加载前的十多分钟里,GTA 5用你的CPU,执行了19.8亿次if命令。
如此简单粗暴的编程思路,让这位老哥哭笑不得:
既然对象有唯一哈希值,那为什么不用hash map???
(hashmap根据hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序不确定。)
至于第一个问题,黑客大哥采用hook大法,不一一读取字符串,而是:
hook strlen
“缓存 “字符串起始和当前长度。
如果在字符串范围内函数在此被调用,返回缓存的值
至于if语句问题,就更直接了——完全跳过重复检查,利用hash map插入项目,因为这些值是唯一的。
最后的结果如下:
现在,GTA 5联机版加载,从原来的6分钟,下降到现在的1分50秒!而且,用的还是七八年前的硬件配置。
在此,应该手动@R星:你学废了吗?