代码优化 5 大原则,第一条就是别优化了!!!
“讓這代碼跑得快一點(diǎn)!!”——我碰到的第一件代碼優(yōu)化任務(wù)就是這么開(kāi)始的。那個(gè)項(xiàng)目是一個(gè)巨大的 SAP 云平臺(tái)應(yīng)用程序,總共含有超過(guò) 3 萬(wàn)行的代碼。
整個(gè) App 加載數(shù)據(jù)的過(guò)程非常之慢,顯然用戶并不喜歡這種體驗(yàn)。
然而,我必須承認(rèn),這個(gè)項(xiàng)目的代碼寫(xiě)的挺不錯(cuò),數(shù)據(jù)庫(kù)調(diào)用很合適,只在有需要的地方進(jìn)行循環(huán),模組化也實(shí)現(xiàn)的很到位。我花了兩天時(shí)間,絞盡腦汁地進(jìn)行各種測(cè)試,審查代碼邏輯,但完全沒(méi)發(fā)現(xiàn)到底是什么地方讓這個(gè)程序變得如此之慢。
就在第三天,在我窮盡了所有的辦法,最后一點(diǎn)理智也快要消失的時(shí)候,我終于發(fā)現(xiàn)了問(wèn)題所在。
在其中的一個(gè)讀取頁(yè)面上,被塞了一個(gè)等待語(yǔ)句,程序到這里就停上 20 秒。
?
這大約是原來(lái)調(diào)試這段代碼的程序員在排查的過(guò)程中插入的等待命令,結(jié)果在將代碼合并進(jìn)生產(chǎn)環(huán)境的時(shí)候忘記把這行東西去掉了。而在生產(chǎn)代碼中,每次調(diào)用讀取的時(shí)候,這段等待命令都會(huì)被執(zhí)行,這就進(jìn)一步放大了產(chǎn)生的問(wèn)題。
于是,我把這行代碼刪掉了。好家伙,一切都正常了!
有人說(shuō),代碼優(yōu)化是一把雙刃劍
優(yōu)化你的軟件是一件好事,但這并不能保證它永遠(yuǎn)都會(huì)有好結(jié)果。
如你是在錯(cuò)誤的原因驅(qū)動(dòng)下,或是通過(guò)錯(cuò)誤的方法進(jìn)行代碼優(yōu)化,這種所謂的優(yōu)化往往可能增加成本,減緩生產(chǎn)速度,甚至可能會(huì)讓軟件的質(zhì)量下降。
此外,大多數(shù)時(shí)候,優(yōu)化并不是沒(méi)有代價(jià)的,你必須做出謹(jǐn)慎的權(quán)衡。例如,提高速度可能會(huì)使你在資源利用方面付出代價(jià),更高效地利用存儲(chǔ)則很容易減慢運(yùn)行速度。你需要仔細(xì)考慮你在其他方面做出的權(quán)衡,這樣你的軟件才能夠?qū)崿F(xiàn)它的主要目標(biāo)。
也許你會(huì)問(wèn),那我該怎么辦?
下面是一些值得你考慮的要點(diǎn),遵循這些原則,可以讓你的代碼更具響應(yīng)性,也能減少你給用戶的設(shè)備以及它們連接到的數(shù)據(jù)庫(kù)帶來(lái)的額外壓力。
1.?不要進(jìn)行優(yōu)化
代碼優(yōu)化的第一條原則就是,“不要”優(yōu)化它。
這個(gè)程序是不是已經(jīng)足夠好了?你要去理解這個(gè)程序?qū)?huì)被如何使用,知道它是在怎樣的環(huán)境下運(yùn)行的,明白如果讓它運(yùn)行的更快到底有沒(méi)有好處。在真正開(kāi)始代碼優(yōu)化之前,你必須要問(wèn)自己這幾個(gè)問(wèn)題。
沒(méi)錯(cuò),代碼優(yōu)化所耗費(fèi)的經(jīng)歷和成本,只有在這樣的情況下是有意義的:
這個(gè)軟件很重要
它運(yùn)行的確實(shí)很慢
在保證代碼健壯、正確、清楚的情況下,它確實(shí)還有改進(jìn)的余地
一個(gè)程序,就算它運(yùn)行得再快,如果無(wú)法得到正確的結(jié)果,那就毫無(wú)用處。有效的優(yōu)化,給軟件帶來(lái)的好處應(yīng)該總要比壞處多。但如果你的優(yōu)化走錯(cuò)了路,那往往還不如別動(dòng)它。
所以,你要做的第一件事,應(yīng)該是設(shè)置一個(gè)合理的優(yōu)化目標(biāo):
你需要清楚地了解你要達(dá)成的目標(biāo)是什么,以及各種優(yōu)化手段與這個(gè)目標(biāo)之間的關(guān)系。
你需要明確而簡(jiǎn)單地說(shuō)明這個(gè)目標(biāo),簡(jiǎn)單到就算技術(shù)理解能力最差的部門(mén)經(jīng)理也能夠理解和復(fù)述它。
你需要在整個(gè)過(guò)程中堅(jiān)持這些目標(biāo)。
要開(kāi)始這項(xiàng)工作,最好的辦法是,根據(jù)對(duì)目標(biāo)的影響確定每項(xiàng)任務(wù)的優(yōu)先順序。你要做的每一件事情,都必須是可計(jì)量的。不要相信直覺(jué),它基本上總是把你引向非常糟糕的方向。
2. 使用一個(gè)分析器
在沒(méi)有經(jīng)過(guò)分析之前,不要貿(mào)然調(diào)整任何東西。最常見(jiàn)的錯(cuò)誤做法就是,花了一整天去重構(gòu)優(yōu)化一段代碼,結(jié)果在運(yùn)行的時(shí)候發(fā)現(xiàn),這段代碼平時(shí)根本用不到。
分析器能精確地測(cè)量出你的程序把時(shí)間都花在什么步驟上了。有些分析器能列出每一個(gè)函數(shù),包括它們被調(diào)用的次數(shù),以及每次執(zhí)行的時(shí)候耗時(shí)的占比等。
還有的分析器能列出每個(gè)命令的執(zhí)行次數(shù),被頻繁執(zhí)行的那些命令,在總占用時(shí)間上的權(quán)重肯定更高,而完全沒(méi)被運(yùn)行的那些命令,往往就是一些無(wú)用的代碼,或者沒(méi)有經(jīng)過(guò)合適測(cè)試的代碼。
一個(gè)好的分析工具,最有用的地方就是能讓你發(fā)現(xiàn)軟件中的“熱點(diǎn)”,也就是消耗了最多運(yùn)行時(shí)間的那些函數(shù)或者命令語(yǔ)句。基本上如果你發(fā)現(xiàn)了一個(gè)熱點(diǎn),你也就發(fā)現(xiàn)了問(wèn)題所在。
性能分析的最佳使用方法就是識(shí)別出“熱點(diǎn)”,然后盡可能地優(yōu)化它們,接著再次測(cè)量,以查看是不是有新的熱點(diǎn)冒了出來(lái)。性能分析的之前分享過(guò)很多,關(guān)注微信公眾號(hào)Java技術(shù)棧可以查找閱讀。
3. 啟用編譯器優(yōu)化
通常情況下,有種比較靠譜的優(yōu)化方式,那就是打開(kāi)編譯器提供的那些內(nèi)置的優(yōu)化選項(xiàng)。
編譯器優(yōu)化通常會(huì)給你的程序帶來(lái)幾個(gè)百分點(diǎn)到兩倍的運(yùn)行速度提升。但某些情況下,這也可能反而降低速度,所以你需要在最終交付之前仔細(xì)測(cè)量性能優(yōu)化的結(jié)果。不過(guò)總的來(lái)說(shuō),現(xiàn)代的編譯器在這方面已經(jīng)做的足夠好了,程序員基本上再也不需要像以前那樣,不停地對(duì)編譯參數(shù)做各種頻繁的小調(diào)整。
一些現(xiàn)代的編譯器還具備全局優(yōu)化能力,可以分析你的整個(gè)程序,以獲得潛在的提升。如果你的系統(tǒng)中有這樣的編譯器,請(qǐng)一定要試試。它可能會(huì)把運(yùn)行時(shí)間減少個(gè)幾秒鐘。
注意:編譯器的優(yōu)化設(shè)置越激進(jìn),最終編譯出來(lái)的程序中出現(xiàn)不明 Bug 的可能性也越高。所以,強(qiáng)烈建議你在開(kāi)啟編譯器的優(yōu)化選項(xiàng)后,務(wù)必重新進(jìn)行回歸測(cè)試,以避免出現(xiàn)一些奇怪的意外。
4. 調(diào)整代碼
只有到這時(shí),你才真正開(kāi)始修改調(diào)整代碼。在此之前,你必須已經(jīng)通過(guò)第二步的性能分析發(fā)現(xiàn)了“熱點(diǎn)”,并且試過(guò)使用編譯器進(jìn)行優(yōu)化——畢竟絕大多數(shù)這些問(wèn)題能讓編譯器幫你解決,也避免了你把這些代碼弄得過(guò)于復(fù)雜。
那么,一般來(lái)說(shuō),有幾種比較成熟的方法來(lái)處理這些“熱點(diǎn)”。再次提醒,你必須非常謹(jǐn)慎,確保在提交每個(gè)更改之前,對(duì)它產(chǎn)生的影響進(jìn)行測(cè)量。
那么,讓我們看看這幾個(gè)方法吧。
將常用的表達(dá)式計(jì)算歸集在一起
如果同一個(gè)非常消耗性能的計(jì)算在多個(gè)地方重復(fù)出現(xiàn),最好能只在一個(gè)地方進(jìn)行計(jì)算,然后記住計(jì)算結(jié)果。除非必要,否則不要在循環(huán)中進(jìn)行這樣的計(jì)算。
用簡(jiǎn)單的計(jì)算代替消耗性能的算法
字符串處理對(duì)于任何一個(gè)程序來(lái)說(shuō),都算是非常常見(jiàn)的運(yùn)算了。但如果你用錯(cuò)誤的辦法去處理字符串,它們也有可能消耗大量的性能。類似的,在某些情況下,你可以用一系列移位操作來(lái)代替乘法運(yùn)算。
但請(qǐng)務(wù)必注意,這種方式或許能帶來(lái)一些性能提升(其實(shí)并不一定),也有可能讓你寫(xiě)出非常崎嶇復(fù)雜的代碼。所以在重構(gòu)的時(shí)候,你必須非常注意代碼可讀性,以免寫(xiě)出無(wú)法維護(hù)的代碼。
消滅循環(huán)
循環(huán),往往是開(kāi)銷最大的行為,沒(méi)有之一。在允許的情況下(例如迭代數(shù)量不太多的時(shí)候),盡量避免使用循環(huán)。
緩存常用的值
緩存能有效地利用本地性——也就是程序(以及用戶)更傾向于重用最近的數(shù)據(jù)。你只需要緩存最常用的字符或數(shù)據(jù),就能大大提高程序的性能。
使用一種更低層次的語(yǔ)言重寫(xiě)
警告:不到萬(wàn)不得已,不要這樣玩。
更低層次的語(yǔ)言在利用硬件設(shè)備性能方面往往更具效率(看看 Python 里的內(nèi)置函數(shù)是用 C 寫(xiě)的就知道了),但要寫(xiě)好這些東西,將會(huì)消耗更多的編程開(kāi)發(fā)時(shí)間。
有時(shí),通過(guò)用低層次的編程語(yǔ)言重寫(xiě)關(guān)鍵代碼,能獲得較大的性能提升,但這是以降低可移植性為代價(jià)的,也會(huì)讓以后的維護(hù)變得非常困難。因此,請(qǐng)謹(jǐn)慎做出決定。
請(qǐng)記住:在優(yōu)化工作中,做出選擇這件事占了90%的權(quán)重。值得花時(shí)間來(lái)決定你要做什么,以及怎樣才能做的對(duì)。當(dāng)然,這也正是編程的黑科技之處!
5. 在你的管理模型中加入代碼審查環(huán)節(jié)
這條是同時(shí)寫(xiě)給開(kāi)發(fā)者和管理者的。對(duì)于軟件工程的管理者,你必須確保代碼審查是項(xiàng)目開(kāi)發(fā)過(guò)程的一部分;對(duì)于開(kāi)發(fā)者,你應(yīng)當(dāng)將代碼審查作為最佳編程做法中的必備環(huán)節(jié)。
推薦看這篇:基于 Gitlab 的代碼審查。
低效的代碼不會(huì)對(duì)系統(tǒng)的日常運(yùn)行造成太大影響。由于這個(gè)明顯的理由,我們往往會(huì)傾向于讓效率低下的代碼通過(guò)審查——因?yàn)樗](méi)有產(chǎn)生任何真正的傷害,不是嗎?這可不對(duì)。隨著時(shí)間的推移,代碼效率將會(huì)越來(lái)越低下,并且導(dǎo)致執(zhí)行速度變慢,最終使客戶端的處理時(shí)間大大超過(guò)可以接受的范圍。
是的,引入常規(guī)代碼檢查,刪除效率低下的代碼片段,或許會(huì)給你增加許多工作量。但從長(zhǎng)遠(yuǎn)來(lái)看,如果你把那些低效的代碼留在原地,未來(lái)你將不得不付出成倍的工作量,去檢查為什么代碼的運(yùn)行要花上這么長(zhǎng)的時(shí)間——那時(shí)的你一定會(huì)感激現(xiàn)在的自己。所以說(shuō),不要讓現(xiàn)在的偷懶成為你未來(lái)的痛苦。盡可能檢查并優(yōu)化你的代碼效率。
一定要讓別人檢查你的代碼。理想的情況下,檢查者是你所欽佩的某個(gè)大佬,但基本上任何開(kāi)發(fā)者都能互相檢查。不過(guò),如果某人根本看不懂你的某些代碼,那可要非常警惕了——要么是檢查者的水平問(wèn)題,要么就是你的代碼可讀性實(shí)在太爛了。
結(jié)語(yǔ)
最后,任何代碼的改進(jìn),都是從你自身開(kāi)始的。在編程的世界里,你不可能從第一遍就非常完美地寫(xiě)出代碼。你總需要對(duì)代碼進(jìn)行更改、修正錯(cuò)誤,甚至有時(shí)代碼無(wú)論如何都無(wú)法按照你想要的方式工作。
這沒(méi)什么問(wèn)題,這完全就是成為一名程序員的必經(jīng)之路。讓寫(xiě)出干凈的代碼,成為你的習(xí)慣吧。
正如極限編程的創(chuàng)始者,設(shè)計(jì)模式的先驅(qū)肯特·貝克(Kent Beck)指出的那樣:“我不是一個(gè)偉大的程序員,我只是一個(gè)不錯(cuò)的程序員,加上偉大的習(xí)慣。”
本文來(lái)源「優(yōu)達(dá)學(xué)城」
原作:Ravi Shankar Rajan ,譯者:歐剃
總結(jié)
以上是生活随笔為你收集整理的代码优化 5 大原则,第一条就是别优化了!!!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Spring Boot 项目打包 + S
- 下一篇: 用代码来说明,为什么需要面向扩展的设计