架構之路(二):效能
似乎程式設計師都是急性子,或許是被windows冗長的開機時間折磨夠了,有可能是因為提公升效能的效果是最顯而易見的……總之,我發現,絕大部分程式設計師對效能的關注和熱情是無與倫比的!
所以直到今天,我仍然看到很多程式設計師無怨無悔的用儲存過程來構建他們的系統,乙個儲存過程可以有幾千行!然後,他們很無辜的問,「業務層有什麼用?究竟能幹些什麼呢?」
在帶團隊的時候,我最怕講的就是效能有關的問題。你要是不談效能呢,那**有時候真心看不下去;你要是強調效能呢,不知道他會給你整出什麼么蛾子出來。其實這就是乙個「度」的掌握,所以非常難以用語言予以表示清楚。所以無數次挫敗之後,我只好咬牙切齒的說,「你的**,只有乙個評判標準,可維護性。效能的問題先不管!」這個答案似乎並不能服眾——尤其是對有上進心的程式設計師而言。
所以,我先專篇講效能,希望能幫助大家更清楚的認識這個問題。
二、所以,在絕大多數情況下,當效能和可維護性相衝突的時候,效能讓位於可維護性。我們採用其他辦法來彌補**效能不夠高的問題。
空洞的說教沒有意義。我們還是舉例來說明吧!
破壞可讀性
前段時間我review**的時候發現,這個程式設計師用linq之後老是用first()而不是single(),我就奇怪了,按業務邏輯,返回的值就應該是乙個,難道可能會是多個,多個應報異常,不應該取first()就完事了呀?想了一會兒,問這個程式設計師,他的回答讓我瞬間一種無力感,「first()效能更高呀!」以下為對話實錄:
「你怎麼知道first()效能更高呢?」我問。
「first()嘛,取了第乙個合格的值就返回,就不會繼續查下去了;single()的話,就會一直查,查出所有資料,然後再取其中的乙個。」
「你確定?你知道有一種東西叫做索引不?」
「啊?……」
然後我簡單的告訴他,索引是一種樹狀結構,可以讓查詢更快等等。
「但我還是覺得應該用first()」,他想了一會兒,還是很堅定。
「為什麼?」,我不明白了。
「就算有索引加快了查詢速度,但用first()在加快了速度上更快呀!更快總是沒錯的吧?」
「……」,我真不知道該怎麼說了,最後突然靈光一閃,「好吧,那你說說,微軟為什麼要搞乙個single()方法出來呢?就為了搞出來誤導你們?讓用first()的產生優越感,嘲笑用single()的?」
他陷入了沉思。
發現同學們還在糾結這個細節。好吧,再解釋一下:
1、你怎麼知道資料庫用的就是mssql呢?你怎麼知道就是用的關聯式資料庫呢?nosql不行麼?所以,你怎麼就知道single()/first()具體是怎麼執行的呢?比如我就要寫個linq實現,把所有的資料全取出來,然後再在記憶體裡排序,最後取first呢?
2、這裡我們考慮可讀性,意思是:讀**時,看到single()就能瞬間知道coder的意思是取唯一的乙個;看到first()就知道coder的意思是要取第一個。和效能沒關係,如果一定要糾纏效能,那好:你要確定唯一性,當然要做檢查(包括不唯一時拋異常),這個效能損失是應該的呀;你要取第乙個,當然要進行排序,排序也會有效能損失呀!
我剛入行的時候,還很是收藏了幾篇文章,比如《高效能程式設計的十大準則》之類的,裡面的內容大致就是,「總是使用stringbuilder,不要使用『+』;總是使用……,不要使用……」。這類文章下面總是有一堆人叫好,「不錯!」,「謝謝分享!」但慢慢的,我就對這些文章產生了懷疑(也應該感謝園子裡的老趙,csdn裡面的sp1234之類的大神);直到很後來,我才明白為什麼這種說法是膚淺的;而只有通過上面的對話,我才能清晰的把我的理解說出來。
所有這些犧牲效能的簡單封裝,都是有其目的的;而其中乙個很重要的目的,就是為了提高可讀性。你為了效能,故意不使用這些現成的封裝,通常,喪失的就是可讀性。
想當然
繼續上面這個例子。最開始的時候,這個程式設計師關於效能的考慮其實是想當然的。這種想當然的情形很多,大致有這幾種:
自己的理解完全就是錯的
自己的理解不能算錯,但實際上底層已經對該問題做了優化
自己的理解沒錯,底層也沒優化
第1、2種比較好理解,第3種為什麼也說他「想當然」呢?因為沒有和硬體環境相契合。
最簡單的例子就是「快取」。比如面試的時候,問你乙個問題,「快取能不能提高效能?」請注意,這是乙個陷阱。答案應該是:「不一定」。幾乎所有的人都認為,快取可以迅速改善效能,是因為今天計算機的cpu和磁碟執行速度,遠跟不上記憶體的發展。但即使如此,無節制的快取,一樣可以拖垮整個系統。
類似的例子還有很多。你沾沾自喜,我節約了一次磁碟讀寫的時候,你同時增加了cpu的負荷;你優化了演算法,減少了cpu的運算,但其實增加了記憶體的壓力……天下沒有免費的午餐。同樣的**,隨著資料的增加,硬體的改變,會呈現出截然不同的效能表現。
所以,開發過程中,很多的「優化」,其實只是你的想當然。與其這樣想當然的優化,不如在拿到效能測試結果之後再有的放矢的進行優化。這時候,又回到了我們之前說的,是不是**的可讀性更重要?這樣你才能迅速的找到該優化的瓶頸啊!否則,一堆亂七八糟看都看不懂的**,你怎麼去優化,你連該優化的點都找不到。
難以維護
噩夢由此開始了。
最後流著淚把辛辛苦苦折騰了好久的**全改回來,就通過資料庫查唄,多麼清晰簡潔的邏輯啊!效能問題?首先,這樣做造成了效能問題麼?然後,就算有問題,用乙個快取能解決不?
合理浪費堆硬體
因為浪費唄!
什麼?你有沒有搞錯?我的**,至少省了一塊記憶體條!那是你還沒從「窮學生」的角色裡轉換過來。你花一周的時間對**進行了優化(就先不考慮你的優化帶來的維護成本增加了),為老闆省下了一塊記憶體條的錢。你以為老闆會拍著你的肩膀表揚你麼?老闆打不死你!
兄弟,賬不是你那樣算的。當你是學生的時候,你的時間成本是0;但你進入工作崗位,每一天都是要發工資的。
通過**來調高效能,是一種無奈——對硬體效能不夠的妥協(參考:80年代遊戲開發者的辛苦困境。這樣寫效能就高,但為什麼現在沒有誰再這麼寫**了?)。否則,絕大多數情況下,堆硬體比優化**的效果好得多,而且便宜得多。硬體的成本按摩爾定律往下降,我們程式設計師的工資也能按摩爾定律減麼?
明明window 10 比window 95更耗效能,為什麼今天沒人用window 95?為什麼vs 2013要10g的空間我們都還屁顛屁顛的趕緊裝上?為什麼現在大家都用c#,沒人用彙編?我們站在人類文明積累的今天,就應該理所當然的享受這一切成果。有打火機你不用,你要鑽木取火。如果你是因為要學貝爺荒野求生裝逼,可以理解;如果你說你是因為怕浪費天然氣,我……我……我怎麼說你呢?「給做打火機的一條活路,行不?」同樣的,程式設計師大神同學,你就當做好事,給下面寫底層做硬體的一條活路吧!你的**都是010001000010000001010101……了,你讓其他人怎麼活啊?
最後,我突然想到的乙個程式設計師為什麼對效能如此敏感瘋狂,對可維護性毫不在意的乙個可能原因:
大家覺得是不是這樣的?所以,願意把**百鍊成鋼繞指柔的人少。想來,是一種莫名的悲哀和淒涼。
架構之路(二) 效能
似乎程式設計師都是急性子,或許是被windows冗長的開機時間折磨夠了,有可能是因為提公升效能的效果是最顯而易見的 總之,我發現,絕大部分程式設計師對效能的關注和熱情是無與倫比的!所以直到今天,我仍然看到很多程式設計師無怨無悔的用儲存過程來構建他們的系統,乙個儲存過程可以有幾千行!然後,他們很無辜的...
架構之路(二) 效能
似乎程式設計師都是急性子,或許是被windows冗長的開機時間折磨夠了,有可能是因為提公升效能的效果是最顯而易見的 總之,我發現,絕大部分程式設計師對效能的關注和熱情是無與倫比的!所以直到今天,我仍然看到很多程式設計師無怨無悔的用儲存過程來構建他們的系統,乙個儲存過程可以有幾千行!然後,他們很無辜的...
Android效能優化之路(二)
上次講到了什麼是android裝置的卡頓以及定位卡頓的原因的工具。這次來看看與螢幕繪製相關。gpu過度繪製 overdraw 是什麼?overdraw是指在一幀的時間內 1 60s 畫素被繪製了多次。理論上的最優是只繪製一次,但是重疊的布局會導致某些畫素被繪製多次,當繪製畫素的時常超過1 60s時,...