日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

性能在軟件工程誕生時就占據著非常重要的位置,如何用更少的硬件資源來支撐更多的功能、來完成更多的任務是軟件工程師的職責,也是用來衡量一個軟件工程師技藝高低的標準。這跟炒菜是一個道理,同樣的食材,在飯店的師傅手里跟普通人手里的做法是有很大的差別的;食堂師傅是工程化的做法,講究是短時間炒制更多的菜,而且味道不能掉;而普通人則比較隨意,沒有什么章法,而且可能只是愛好或者放松的渠道,所以不用考慮炮制的性能如何,能不能達到預計的目標。簡單來說,廚師是靠手藝去賺錢,而其他人只是玩玩。那么程序員的手藝體現在哪呢?可以肯定的說,性能調優是最重要的一環,也是最基本的一環,甚至我覺得只有掌握這個技能后才能夠進階做構架或者做設計,否則如果反其道而行之則會舉步維艱。

這時你可能會說,我是覺得性能是很重要,但是怎么去學呢?IT是個發展迅速的行業,每年涌現的新技術,新框架數不勝數,多如繁星,往往還沒學懂JAVA,Go都快被淘汰了……一茬接一茬,無從下手。如果你常常有這個感覺,那么這篇文章可能對你有幫助。

性能優化的套路

先說結論,性能優化或者說如何能夠開發出效率更高的程序來(當然硬件資源一樣且使用率一樣的條件下),只有兩條路:

1. 掌握局部性原理

2. 掌握基本的算法設計與分析

在現有計算機構架下(馮諾依曼構架)這是調優的充分必要條件,換句話說就是,如果你寫了一個性能比別人好的程序,那么只有兩種情況,要么是局部性原理用得更好,要么是就是采用了更好的算法;同時,如果你想要寫出比別人好的程序,那么你也只有兩條路可以走,運用更底層的局部性優化設計,或者設計更好的算法來解決這個問題。

局部性原理

如果你在軟件工程領域“閱歷”足夠豐富,你就會有恍然大悟的感覺,因為這種例子實在太多:linux中的硬盤緩沖、頁緩存,redis中元素較少的時候用ziplist代替hashmap可能還會帶來性能的提升,內存的減少;Kafka依賴的磁盤緩存,分區存儲機制,MySQL的B+樹索引;更底層的CPU的Cacheline技術,分頁技術等等其實都是用了局部性原理來作為理論基礎的。那么什么是局部性原理呢?

簡單來說分為三個要點:

  1. 程序總是按照“批”來處理數據,這樣效率最高;比如:CPU讀取內存是按照批來處理如:一個cacheline=64/128byte,內存與硬盤按照4KB來讀取。因為計算機結構的特點,各級設備(CPU到內存到硬盤與外設)之間的訪問速度是有數量級的差別;所以就注定不能一個個字節來讀?。贿@個現象決定計算機優化的方向,被稱為馮諾依曼瓶頸;
  2. 最近訪問過的資源(內存位置),可能近期內還會被訪問,這個叫做時間局部性,對應的解決方案是緩存;
  3. 當前位置內存被訪問過,那么大概率旁邊的內存也會被后續訪問,這個叫做空間局部性,對應的解決方案是預加載;

所以推導的順序是:正是因為現代計算機有馮諾依曼瓶頸所以需要用緩存與預加載來提高程序的性能,否則會因為IO過重,資源得不到利用而效率偏低。

還是拿做飯這件事舉例子。當我們去買菜的時候,會盡可能的減少去菜市場的時間,也肯定不可能發現什么菜沒有了再臨時去菜市場購買,這樣“IO”就太重了,要使用局部性原理來解決。就炒菜的例子來說,我們要炒青椒炒肉這個菜;首先是買菜,我們發現在買青椒的時候可以把蔥姜蒜也都一并買了,因為蔬菜區往往是相鄰的;而買肉的時候,盡量也把醬油醋都一并買了,它們也是很可能也是挨著的;這種在空間上相鄰的提前加載這就是“預加載”了;而回到家,我們開始炒菜的時候,廚房的布置往往是炒菜的鍋子跟油鹽醬醋是分開放置的,但是炒菜的時候要經常加鹽、放醬油,所以為了減少來回取油鹽的時間,會在炒菜的時候將配菜與油鹽放置在炒鍋的附近,加快炒菜的速度,等炒完后又重新放回去,這個就叫做“緩存”的思維——把經常使用的資源放在工作較近的地方,等使用完再釋放。

在Linux內核中,我們可以看到很多的buffer與cache,這些數據結構都是局部性原理的具體使用實例。比如,我們知道內存跟磁盤之間IO速度相差5個數量級。那么往往會出現,一個進程剛寫入磁盤的數據,又要被其他進程讀取,那么一來一回CPU都在等待磁盤讀取數據,十分浪費資源,那么如果將磁盤的數據放入磁盤寫buffer,讀的時候優先從buffer中讀取,而buffer都是在內存中,這樣就彌補了這個性能的gap,這就是“時間局部性”原理的使用,也就是我們常說的“緩存”,或者“空間換時間”;

Mysql數據庫有個特點就是數據是存在磁盤上,讀取記錄時需要將數據從磁盤加載到內存然后進入CPU計算得出結果,所以會橫跨三個IO邊界——磁盤、內存與CPU,它們之間都有好幾個數量級的延遲,所以IO性能的平衡就十分的重要。其中比較典型的就是B+樹這種針對外部存儲型的數據結構的引入,它十分貼合磁盤IO的“口味”。磁盤IO有個特點,就是讀取寫入都很慢,但是可以一次讀取大量的數據。根據這個特點,B+樹采用多叉樹的結構進行設計,這樣做相比二叉樹來說可以減少樹的高度,這樣帶來的好處是每一層數據都可以是在物理空間相鄰的,從而可以通過最少的IO次數加載更多的數據到內存;而這些數據往往是業務相關的,所以一次IO讀取的數據可以供后續很多計算工作使用而不用重復IO。比如,一個表有20列,那么每一行數據就有20個字段,這些字段往往在磁盤中是連續存放的,當我們通過id查詢其中某個字段a的值時,Mysql其實會將整個記錄都取出來,加載到內存,而程序訪問完a字段,再訪問記錄中其他字段時就不需要磁盤IO了,直接讀取內存中記錄的緩存就行,這就是“空間局部性”的使用實例,也就是我們常說的“預加載”,系統預熱。

算法分析與設計

這是計算機科學的范疇,也是最引人注目的領域,就好比武俠小說里面的武功,華麗的招式比不過強大的內功,練招式容易練內功難,一旦內功深厚,招式什么的都是分分鐘被秒殺,就好比《一拳超人》中的琦玉老師,專治各種花里胡哨。而我想說的是,算法設計技術就是軟件工程領域的內功心法。當然我遠遠沒有達到可以對這個領域品頭論足的程度,就想拋磚引玉,介紹點皮毛,希望對有天賦的同學有所啟發。

數據結構

數據結構之所以重要是因為不同的數據結構表現出來的數學性質是構成特定算法的基礎。就好比各種化學元素可以搭建各種性質不同的化合物,而不同性質的化合物正式化學工程所依賴的基礎。計算機科學的數據結構可以大致分為兩種,一種是數組,另一種是鏈表。兩種性質截然相反的“物質”。

  1. 數組——線性性能最好,支持隨機訪問,按照索引取數組中的元素時間復雜度是O(1),而插入與刪除元素時間復雜度是O(n);
  2. 鏈表——擴展性能最好,支持動態的增減元素,插入、刪除元素的時間復雜度是O(1),而檢索元素的時間復雜度變成了O(n);

(注:O標記是數學語言,用于標記一個函數的增長快慢,是一個漸進界函數,具體細節可以參考屈婉玲教授的詳細解釋)

這個世界有時候是驚人對稱的,對稱是美妙的。這樣一對性質完全互斥的結構就是構成所有高等數據結構的基礎。就像中國傳統文化中的太極一樣,你中有我我中有你,互相補充,世間萬物的運行法則皆可解釋。

舉幾個例子,有些高等數據結構具有耀眼的數學特性,比如“堆”,或者叫做優先級隊列、二叉堆,就是以數組作為基礎的。它在插入、刪除、查詢元素時的性能可以穩定在O(logn)量級;在互聯網行業中,只有logn級別的算法可以適用,因為當數據規模急劇增加的時候,對數函數能夠很好的平穩壓力,所以,"logn"的算法對互聯網行業貢獻巨大,具有整流器的作用。比如,在微服務流量控制,大數據流處理、topN、高性能定時器都有很多應用。而堆只有在數組實現的算法中才能保持這個特性,如果用鏈表就會退化為O(nlogn),失去魔力。

另一個例子是二叉樹,這就是鏈表的一個使用場景。二叉樹是一種樹狀結構,其中平衡二叉樹在插入與刪除的過程中只要移動logn次就能找到自己的新位置,而且代碼簡單易于維護(不容易寫錯也是工程中一個重要的考慮點,如果寫得代碼過于復雜就要反思下是否使用錯了數據結構)。比如:紅黑樹就是一個綜合性能很好的平衡二叉查找樹;它是一個動態的數據結構,可以在動態添加與查找過程中穩定在O(logn)量級;在Linux內核中大量使用;而且在Java中的ConcurrentHashMap在沖突大的情況下,沖突元素大于8也會升級成紅黑樹存儲沖突元素,來平衡工程與算法效率之間的矛盾。

當然,數據結構是可以融合的,比如Java中的LinkedHashMap就是融合了數組、哈希表與鏈表的優秀實踐。普通的哈希表因為通過哈希函數將元素鏈接到數組的索引號上面,實現高速的查找性能,但是丟失了元素的插入順序,而有時候我們需要這個順序性來實現特殊的需求,比如緩存淘汰策略;而如果使用鏈表,形成一個插入的隊列,先插入的在隊列頭,后插入在隊列尾部;但是這樣雖然保存了插入的順序但是丟失了查找性能;為了平衡,我們可以在哈希表的基礎上,每個元素再增加一個指針用來連接前后插入的元素,形成隊列,這樣沒插入一個元素不僅在哈希表上掛載新的索引點,還要將新元素掛接到隊列的尾部,而每一步都是O(1)的開銷,是可以接受的,這就是LinkedHashMap的實現原理。

算法設計技術

算法設計技術是使用數據結構解決實際問題的技術,就好比化合物特性研究與化學工程的關系,一個是偏重科學特性的研究,一個是解決實際問題。為了能夠讓科學能夠指導生產,能夠造福世間,必須將科學落地,那么這個過程中最重要的一步就是對實際問題的建模,建模是對問題的模擬,越準確越能夠解決好問題。那么,在算法設計層面有哪些建模方式呢?大體可以分為三個:

  1. 蠻力算法
  2. 貪心算法
  3. 分治算法
  4. 動態規劃

其中,蠻力算法是對問題建模的初期階段,是對問題的程序再現,追求的是定性,性能不一定重要,但是問題描述沒問題;分治與貪心是在蠻力基礎上的一次降階,往往可以將問題優化到O(nlogn)的規模以內;而動態規劃可以進一步將問題的階降到O(n)級別。降階是設計算法的初衷,前提是問題本身計算的各個階段是有冗余的,有重復計算的地方,而找到這個重復的點并不容易,就拿動態規劃來說吧,雖然有極致的性能但是發現遞推方程并不容易,當然這一切都要經過嚴格的數學證明才行,這就更加難能可貴。我們這里沒有考慮空間的優化,往往降階的過程中最好保證空間的復雜度不會激增,這樣才會有效。這方面我們不展開了,有很多資料可以參考,其中我推薦屈婉玲教授的算法設計分析「鏈接」 。

寫在最后

細心的你可能會發現,為什么沒有講多線程?多線程也是常用的優化手段啊。對,工程上多線程往往可以優化程序的運行效率,但是這里有個基礎就是硬件的資源并沒有被充分利用,也就是說,如果因為IO導致CPU利用率不足,當然我們就要想辦法去構建可以充分利用硬件資源的方法,而多線程、多路復用這些技術都是為了提高硬件的利用“帶寬”的技術,所以并不在我們討論范圍之內。這篇文章還有更加詳細的另一篇姐妹篇,在這里「鏈接」,希望對你有所幫助。


文/ThoughtWorks 肖勁

分享到:
標簽:優化
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定