03.07 收藏!代碼調優的6大法則都在這裡了

收藏!代碼調優的6大法則都在這裡了


數學家高斯在小學的時候,老師要求從1+2+3開始一直加到100,得出的和是多少?其他同學都費勁地一個數一個數的加,只有小高斯注意到了這些數可以兩兩配對,相加和為101,所以想到一共有50對101,求和可以用乘法:50X101=5050。


有沒有感受到數學思維的強大?其實,編程中的代碼優化,就與數學公式有著異曲同工之妙。


小白與大神寫同一個程序,小白跑200萬條數據,需要8小時,而大神在進行代碼優化後,只需要40分鐘,效率提高了1200%!


要成為金字塔尖的程序員,不僅要關注程序能不能運行,更要關注程序的運行效率,在每一個字符,每一段語句下功夫,這就是代碼調優。為了幫助大家成為一個注重細節的程序員,來看看以下6大代碼調優法則!


代碼調優6大法則


空間換時間法則


修改數據結構。為了減少數據上的常見運算所需要的時間,我們通常可以在數據結構中增加額外的信息,或者修改數據結構中的信息使之更易訪問。


存儲預先計算好的結果。對於開銷較大的函數,可以只計算一次,然後將計算結果存儲起來以減少開銷。以後再需要該函數時,可以直接查表而不需要重新計算。


高速緩存。最經常訪問的數據,其訪問開銷應該是最小的。


懶惰求值。除非需要,否則不對任何一項求值。這一策略可以避免對不必要的項求值。


時間換空間法則


堆積。密集存儲表示可以通過增加存儲和檢索數據所需的時間來減少存儲開銷。儘管堆積有時通過犧牲時間來獲取空間,但是這種較小的表示方式處理起來通常更快。


解釋程序。使用解釋程序通常可以減少表示程序所需的空間,在解釋程序中常見的操作序列以一種緊湊的方式表示。


代碼空間技術。有時候空間的瓶頸不在於數據,而在於程序本身的規模。在過去的艱苦年代,我見到的圖形程序通篇都是類似下面的代碼:

格式 1 ( 15px, 粗體, #3E3E3E )


<code>for i = [17, 43] set(i, 68)
for i = [18, 42] set(i, 69)
for j = [81, 91] set(30, j)
for j = [82, 92] set(31, j)/<code>


其中set(i, j)“點亮”屏幕位置(i, j)處的圖形元素。使用適當的函數,例如用於繪製水平線的hor函數和繪製垂直線的ver函數,就可以使用如下所示的代碼替換上面的代碼:


<code>hor(17, 43, 68)

hor(18, 42, 69)
vert(81, 91, 30)
vert(82, 92, 31)/<code>


上述代碼又可以用一個解釋程序來替換,這個解釋程序從類似下面的數組中讀取命令:


<code>h 17 43 68
h 18 42 69
v 81 91 30
v 82 92 31/<code>


如果上面的代碼仍然佔用太多的空間,那麼可以為命令(h、v或兩個其他命令)分配兩個位,併為後面的三個數(這些數是範圍0~1023內的整數)各分配10個位。於是,上面的每一行都可以用一個32位的字來表示(當然,這種轉換應該由程序來進行)。這種假設的情況揭示了用於節省代碼空間的幾種通用技術。


收藏!代碼調優的6大法則都在這裡了


函數定義。通過用函數替換代碼中的常見模式可以簡化上述程序,相應地也就減少了它的空間需求,並增加了其清晰性。這是一個“自底向上”設計的普通例子。


儘管我們不能忽視自頂向下的方法,但是由良好的原始對象、組件和函數所給出的均一的視圖可以使系統維護起來更加簡單,同時也節省了空間。微軟刪除了很少使用的函數,將它的整個Windows系統壓縮為更加緊湊的Windows CE,使其能在具有更小內存的“移動計算平臺”上運行。


更小的用戶界面(UI)在窄屏幕的小型機器(範圍從嵌入式系統到掌上電腦)上運行得很好,熟悉的界面對用戶來說非常方便。更小的應用編程接口(API)使得系統對於Windows API程序員來說很熟悉(並且對於許多程序來說,即使不兼容,也非常接近)。


解釋程序。在圖形程序中,我們用4字節的解釋程序命令替換了一長行的程序文本。描述了一個用於格式信函編程的解釋程序,儘管它的主要目的是使編程和維護更加簡單,但是它同時也減少了程序的空間。


Kernighan和Pike在他們Practice of Programming一書介紹了“解釋程序、編譯器和虛擬機”。他們列舉了許多例子來支撐他們的結論:


“虛擬機是以前的一個有趣想法,最近藉助於Java和Java虛擬機(Java Virtual Machine, JVM)又重新流行起來了;對於高級語言編寫的程序來說,它們很容易提供可移植的、高效的表示。”


翻譯成機器語言。在節省空間方面,大多數程序員都較少控制的是將源語言轉換成機器語言。對編譯器進行一些微小更改可以將Unix系統早期版本的代碼空間減少5個百分點。


作為最後的手段,程序員可能會考慮到將大型系統中的關鍵部分用匯編語言進行手工編碼。這個高開銷、易出錯的過程僅能帶來一點點好處;不過,該方法還是常常用於一些內存寶貴的系統,比如數字信號處理器。


Apple Macintosh於1984年誕生,當時是一款令人稱奇的機器。這款小小的計算機(128 KB RAM)具有令人震驚的用戶界面和功能強大的軟件集。設計小組預期將製造好幾百萬臺這樣的機器,並且只提供64 KB的ROM。


通過謹慎的函數定義(包括泛化運算符、歸併函數和刪除功能特性)並使用匯編語言手工編碼整個ROM程序,該小組將令人難以置信的眾多系統功能集成到了一個極微小的ROM上。


他們估計那些經過極度調優的代碼(具有謹慎的寄存器分配和指令選擇)的規模只有從高級語言編譯過來的等價代碼的一半(儘管那時編譯器已經有了很大的改進)。緊湊的彙編代碼運行起來也非常快。


循環法則


將代碼移出循環。與其在循環的每次迭代時都執行一次某種計算,不如將其移到循環體外,只計算一次。


合併測試條件。高效的內循環應該包含儘量少的測試條件,最好只有一個。因此,程序員應儘量用一些退出條件來模擬循環的其他退出條件。


循環展開。循環展開可以減少修改循環下標的開銷,對於避免管道延遲、減少分支以及增加指令級的並行性也都很有幫助。


刪除賦值。如果內循環中很多開銷來自普通的賦值,通常可以通過重複代碼並修改變量的使用來刪除這些賦值。具體說來,刪除賦值i = j後,後續的代碼必須將j視為i。


消除無條件分支。快速的循環中不應該包含無條件分支。通過“旋轉”循環,在底部加上一個條件分支,能夠消除循環結束處的無條件分支。該操作通常由優化的編譯器完成。


循環合併。如果兩個相鄰的循環作用在同一組元素上,那麼可以合併其運算部分,僅使用一組循環控制操作。


收藏!代碼調優的6大法則都在這裡了


邏輯法則


利用等價的代數表達式。如果邏輯表達式的求值開銷太大,就將其替換為開銷較小的等價代數表達式。


短路單調函數。如果我們想測試幾個變量的單調非遞減函數是否超過了某個特定的閾值,那麼一旦達到這個閾值就不再需要計算任何變量了。該法則的一個更成熟的應用就是,一旦達到了循環的目的就退出循環。


對測試條件重新排序。在組織邏輯測試的時候,應該將低開銷的、經常成功的測試放在高開銷的、很少成功的測試前面。


預先計算邏輯函數。在比較小的有限域上,可以用查表來取代邏輯函數。


消除布爾變量。我們可以用if - else語句來取代對布爾變量v的賦值,從而消除程序中的布爾變量。在該if - else語句中,一個分支表示v為真的情況,另一個分支表示v為假的情況。


過程法則


打破函數層次。對於(非遞歸地)調用自身的函數,通常可以通過將其改寫為內聯版本並固定傳入的變量來縮短其運行時間。


遞歸函數轉換。遞歸函數的運行時間往往可以通過下面的轉換來縮短。將遞歸重寫為迭代,通過使用一個顯式的程序棧將遞歸轉化為迭代。(如果函數僅包含一個對其自身的遞歸調用,那麼就沒有必要將返回地址存儲在棧中)。


如果函數的最後一步是遞歸調用其自身,那麼使用一個到其第一條語句的分支來替換該調用,這通常稱為消除尾遞歸。解決小的子問題時,使用輔助過程通常比把問題的規模變為0或1更有效。


並行性。在底層硬件條件下,我們構建的程序應該儘可能多地挖掘並行性。


表達式法則


編譯時初始化。在程序執行之前,應該對儘可能多的變量初始化。


利用等價的代數表達式。如果表達式的求值開銷太大,就將其替換為開銷較小的等價代數表達式。用加法替代乘法,降低數組元素上的循環強度。很多編譯器進行了這一優化。這種方法可以推廣為一大類增量算法。


消除公共子表達式。如果兩次對同一個表達式求值時,其所有變量都沒有任何改動,那麼我們可以用下面的方法避免第二次求值:存儲第一次的計算結果並用其取代第二次求值。


成對計算。如果經常需要對兩個類似的表達式一起求值,那麼就應該建立一個新的過程,將它們成對求值。如果insert函數的參數已經在集合中,C++代碼就使用不完成任何操作的insert替代這兩個函數。


利用計算機字的並行性。用底層計算機體系結構的全部數據路徑寬度來對高開銷的表達式求值。


看完這6大代碼調優法則,是不是恍然大明白的?有時候,你不是不夠努力,而是用錯了方法,

學好《編程珠璣》中的方法論,更有效率地攀登程序員進階之路!

收藏!代碼調優的6大法則都在這裡了


編程珠璣 第2版

作者:[美]喬恩·本特利(Jon,Bentley)

京東

本書是計算機科學方面的經典名著。書的內容圍繞程序設計人員面對的一系列實際問題展開。作者以其獨有的洞察力和創造力,引導讀者理解這些問題並學會解決方法,而這些正是程序員實際編程生涯中至關重要的。


本書的特色是通過一些精心設計的有趣而又頗具指導意義的程序,對實用程序設計技巧及基本設計原則進行了透徹而睿智的描述,為複雜的編程問題提供了清晰而完備的解決思路。本書對各個層次的程序員都具有很高的閱讀價值。


-END-


分享到:


相關文章: