一次性能優化

1、先說背景

      導入一個文件,文件內容爲20萬個手機號碼,將這20萬個手機號碼插入到數據庫中。

      整個過程:a.解析文件;b.讀取每行數據;c.讀取到的行數據放入一個LinkedList中;d.遍歷這個LinkedList,把通過校驗的元素放入新的一個LinkedList中;e.遍歷這個新的LInkedList,逐個元素進行插入數據庫操作

     一次操作耗時約2~3分鐘

2、尋找問題

     通過對過程的概述,可以發現有3個顯著的問題,a.用了2個LinkedList(空間消耗);b.遍歷了2次LinkedList(時間消耗);c.執行了20萬次的數據庫插入操作(IO消耗)

3、產生問題的原因

     空間消耗雖然沒有對總耗時造成多大的影響,但是考慮到內存消耗情況,也是應當進行解決的。

     2次遍歷的時間消耗,跟進去看代碼發現遍歷的時候,使用的是for(int i=0;i<list.size();i++),然而,使用的list卻是一個LinkedList,通過這種方式來進行遍歷的話,其時間複雜度爲 O(N²),這就是爲什麼會慢的原因了。

     插入數據庫的IO消耗,在遍歷的時候(依舊使用的是for(int i=0;i<list.size();i++)進行遍歷的),對每個從list中取到的數據進行insert()操作,每次操作都會伴隨着 a.獲取連接;b.開啓事務;c.執行插入操作;d.提交事務;e.關閉連接 等一系列的數據庫IO操作,然後這種系列的操作還反覆執行20萬次,這樣必然會變慢。

4、解決方法

     空間消耗,在讀取文件的時候,就對讀取到的行數據進行處理,將符合規則的數據存入list中,這樣不僅可以減少一個list,還可以減少一次list的遍歷。

     時間消耗,遍歷方式進行一種改變,將其變爲for-each的方式,或者在選擇容器時,就使用ArrayList來代替LinkedList,這2種修改的方式都可以讓其在時間複雜度上爲O(1)。

     IO消耗,每次插入一條數據,這樣必然是不可取的,將其從單條插入改爲批量進行插入,減少數據庫的IO次數,這樣IO次數越少,時間消耗上面就會降低。

5、探究解決問題的背後

     把LinkedList變成ArrayList

     由於是數組了,根據尋址公式,可以馬上獲取到結果,時間複雜度上爲O(1),但是也是由於是數組,添加時就會增長,每次擴容時就會產生一整塊一整塊的垃圾在內存中,當年輕代滿了,MinorGC還沒有觸發時,直接進入老年代,在某種極端情況下,老年代碎片化嚴重,沒有那麼大的一整塊內存空間分配時,觸發Full GC,垃圾回收器如果是用的默認的Parallel Old,其特點是併發回收垃圾,但是會停止工作線程,造成系統卡頓,嚴重時這個卡頓效果就會持續很長一段時間;如果使用的是CMS,垃圾回收時,工作線程也是可以繼續工作的,但是遇到極壞的情況下,CMS無法完成時,會調用Serial Old來進行處理,由於Serial Old的特性是單線程的,並且回收時也是會停止工作線程的,造成的卡頓效果將大大的延長。

     把for循環改爲forEach

     通過迭代器來遍歷,迭代器中存儲着後續元素的索引,在時間複雜度上爲O(1)。LinkedList是一個雙向鏈表,會多存儲2個前驅和後驅的指針,內存佔用上來說,是比ArrayList多了不少,但是它不需要擴容、不需要連續的內存空間,擴容會帶來垃圾,如果在MinorGC觸發前,程序已經執行完成了,那麼,LinkedList所造成的內存消耗,是否又比ArrayList少了一些呢?

     IO消耗問題

     執行了20萬次的insert操作,需要頻繁獲取Session,獲取連接,然後每插入一條就要提交一次,數據庫又是基於文件系統的,每一次insert操作,就是要向文件中寫入數據,都會進行一次用戶態到內核態的切換,每次切換都會執行中斷指令,等到內核態切換爲用戶態時再從中斷服務中恢復用戶進程。程序和數據庫建立連接是一次IO操作,數據庫再和操作系統交互又是一次IO,然後再乘以20萬次,所以會慢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章