本文已同步發表到個人技術微信公衆號,掃一掃文章底部的二維碼或在微信搜索 「程序員驛站」便可關注,天天都會更新優質技術文章。同時,也歡迎加入QQ技術羣(羣號:650306310)一塊兒交流學習!程序員
這篇文章是繼「Android電量優化全解析」、「Android內存優化全解析」、「Android渲染優化解析」以後關於Android性能優化的第四篇原創文章,主要講解了Android計算優化相關知識點,但願對你們有所幫助。今天我講述的內容按照下面的結構進行。算法
讓咱們首先從一個很是熟悉的問題開始,也就是函數執行過慢的問題,這是性能概念方面的初級基礎計算機科學知識。有時候,當你寫完代碼以後,你發現它的運行速度,比你預想的要慢,這種狀況常常會平白無故地出現,你專一於採用某種方法編寫代碼,來解決特定的問題,可是很快你發現代碼的運行時間過長,遠遠超過你的預期。 代碼運行時間過長的問題,在很大程度上能夠歸咎於編程語言,固然還有相關硬件執行代碼的方式。例如,在一些老舊硬件上,執行浮點比較算法分支語句所花的時間,幾乎是整數或布爾數值的四倍。其緣由在於芯片架構,負責浮點計算的CPU部分在分支邏輯階段以後開始工做。這意味着任何浮點比較都須要等待,直到循環管道結束,從而拖延其餘運算,直到分支邏輯最終執行完成,可是請不要感到懼怕。現代硬件一般不須要處理這種細微問題,可是這也說明了一個很重要的觀點,即,你編寫代碼的方式會影響性能,具體視硬件上執行的編程語言而有所不一樣。這個問題甚至能夠追溯到芯片架構。我想要說的是,爲了優化你的代碼,你須要理解系統如何運行代碼。數據庫
緩慢的函數執行一般是因爲兩方面的問題形成的,第一種是執行速度很慢的函數,這種函數很容易被發現。你的某些函數所花費的時間,超過你的預期2倍、10倍,甚至50倍,這種問題容易解決。只要找到那些運行很慢的函數,查看代碼找到問題所在,而後想辦法解決就能夠了。更難發現的是第二種類型,千方百計也難以發現,尤爲是當你有數以千計的函數時,每一個函數所用的時間都額外增長一毫秒,從而致使整個程序執行速度變慢數百毫秒,這種類型的問題很難跟蹤,並且更難以解決,由於一般你須要分析,每段執行代碼才能發現這些小問題,這最終會影響你的產品發佈,進而影響公司業績。編程
要解決這些細小的問題,主要的方法是剖析(profiling),經過時間線分析方法,找到執行速度緩慢的代碼部分,或者時間明顯長於其它代碼的部分,而後進行一些細小變動。而後再次進行時間線分析,找到執行很慢的函數以後,你須要對這些函數的代碼行進行時間線分析,以及調用這些函數的全部其餘函數。這項工做確實至關繁瑣,除非你是這個領域的專家,可是不要懼怕,Android SDK有一些很不錯的工具,幫助你找到這些有問題的代碼部分,讓你可以當即解決它們,讓咱們來了解它們。數組
將演示如何跟蹤你的應用程序中的一些計算相關的性能問題。在這個演示中,咱們將使用工具來跟蹤Sunshine應用程序,這個工具是TraceView。咱們載入它,打開DDMS視圖選擇咱們要分析的應用,請你注意一下,工具欄上的一些圖標,尤爲是這個圖標。看上去像是三面箭頭,上面有紅色的圓點,若是按這些按鈕,會出現一些提示,說將開始進行方法分析。這是TraceView的啓動方法,咱們點擊它。將出現一個彈出窗口,提示有兩種方法來分析你的應用程序。你能夠記錄每一個方法的輸入和輸出,他們對資源的要求很高,或者,你也利用示例進行一些分析。其含義是,默認狀況下分析程序,將會每1000毫秒偵測一次你的應用程序,以發現和記錄實際上在運行的功能,如今,讓咱們來使用這些默認設置。我點擊一下OK,既然分析程序已經在繼續,咱們就與你的應用程序進行交互,看可否記錄一些動做。所以跳轉到這裏與Sunshine應用程序進行交互,好極了,山景地區的天氣。不幸的是,週末的天氣卻不太好,將會下雨。咱們看看海岸區域,咱們在南加州的朋友還好嗎?奇怪,他們已經進入冬季,這在聖地亞哥是不多見的。咱們回到Android Device Monitor,咱們想要中止分析。咱們仍是應該點擊這個圖標,啓動時也是點擊它。在最上方有一個黑色圖標或者黑色方塊,點擊它能夠中止分析。如今,可能須要一點時間來載入跟蹤記錄,將會顯示在窗口上方,選項卡的這個位置。請記住,實際所用的時間可能更長一點,具體取決於實際記錄的內容。緩存
咱們來看跟蹤視圖,跟蹤視圖有兩個主要組成部分。上方窗格的名稱是timeline面板,下方窗格內有不少的信息,稱爲profile面板。這個時間線可以很好的顯示代碼的執行狀況,這裏顯示的每一行,實際上對應於一個線程。顯示的每個顏色,對應於一個正在運行的特定方法。例如,咱們能夠看到,主線程的全部活動,咱們能夠看到方法啓動和中止時間點,更有用的是放大這裏,找到特定的方法,瞭解他們是如何執行的。它們會以這種U型模式顯示出來。這裏的條形表示,方法的啓動時間。右側的條形表示,方法的中止時間。條形的寬度表示方法執行所用的時間。如今,咱們選擇一個特定的方法,咱們跳轉到跟蹤視圖窗口的底部,這裏,咱們看到一些分析數據顯示出來。咱們能夠看到哪些方法調用了咱們選定的方法。在這裏,用他們的父級方法顯示爲藍色,咱們還能夠看到一些信息,顯示在這個方法內調用了哪些方法。也就是說,咱們調用了一個發送輸入事件方法。在選擇以後,會顯示一個本地條柱。對於咱們選擇的全部這些方法,都有大量的附加統計信息。例如,能夠看到獨佔CPU時間,咱們可使用這些信息,找到具體方法的特定問題。非獨佔CPU時間是特定方法在其內部調用的全部方法所用的時間。這能夠幫你在信息樹內找到,選定方法的特定問題。另外一個十分有用的信息是,方法被調用了多少次,或者遞歸調用自己多少次。若是咱們滾動到右側,咱們能夠找到這些信息,這裏有一個列名爲"calls and recursion",此列顯示方法被調用多少次,或者它被遞歸調用多少次。在這個分析面板中,有大量的附加信息。另外,不要忘記這個搜索框,它能夠幫助查找你所關心的功能。性能優化
列名 | 做用 |
---|---|
Name | 該進程運行過程當中所調用的函數名 |
Incl Cpu Time | 函數佔用的CPU時間,包含內部調用其它函數的CPU時間 |
Excl Cpu Time | 函數佔用的CPU時間,但不包含內部調用其它函數所佔用的CPU時間 |
Incl Real Time | 函數運行的真實時間(以毫秒爲單位),內含調用其它函數所佔用的真實時間 |
Excl Real Time | 函數運行的真實時間(以毫秒爲單位),不包含調用其它函數所佔用的真實時間 |
Calls+Recur Calls/Total | 函數被調用次數以及遞歸調用佔總調用次數的百分比 |
Cpu Time/Call | 函數調用CPU時間與調用次數的比(該函數平均執行時間) |
Real Time/Call | 同CPU Time/Call相似,只不過統計單位換成了真實時間 |
備註: CPU time就是CPU實際花了多少時間在運行函數,CPU在多進程環境下不會把全部時間用在一個進程上的服務器
我想向大家介紹我最喜歡的兩個性能技術,批處理(batching)和緩存(caching)。前面咱們已經說過一些函數和運算,須要很是大的資源開銷,這也會影響計算性能。例如,在執行以前把數據載入特定的內存區域,或者,在搜索以前對數值集進行排序,在執行屢次以後,並且次數確實是個很大的數字,資源開銷將會嚴重影響應用程序的性能。批處理是能夠幫助解決這種性能問題,它消除每一個運算的獨立執行開銷,好像是全部人都開一輛車,而不是每一個都開一輛,從而節省汽油。這種狀況最多見於在執行運算以前,你須要準備數據。例如,在查找集合中的值時,最有效的方法是進行排序,而後進行二分法搜索等等。有一點必須弄清楚,這並非最有效的方法。這只是舉一個例子而已,最簡單的方法是寫一個函數,提供一個集合和一個值,對集合進行排序,而後查看值是否存在於集合之中。對於某些性能要求來講,這樣作是能夠的。可是,若是有10000個值,並且總共須要數百萬組數據,排序所花費的時間,將會增長不少倍,答案很明確。對這組數據一次性完成排序,而後查找全部10000個值,並非明智的方法。這時就須要用到批處理,咱們找到重複的運算,找到以後,進行批處理。微信
緩存是與批處理類似的概念,這也是目前爲止,你能理解的最重要的性能技術。這項技術全面地推進現代計算機科學的發展,以計算機爲例,內存的做用是用來存儲信息,讓CPU可以更快的訪問數據,其速度遠快於訪問硬盤數據。網絡
或者以網絡爲例,世界各地存在大型服務器倉庫,它們被稱爲數據中心,它們的做用是存儲火緩衝被頻繁訪問的內容。這樣,你的計算機就沒必要每次都訪問遠在12000英里以外的服務器。你在埃及的朋友可能在這個服務器上發佈了一張圖片,固然,若是你在埃及,這樣的緩衝服務器可能就沒有什麼意義,可是你已經明白其中的道理。以代碼爲例,最多見的緩存優化一般涉及屢次計算,可是若是始終相同的數據,例如,在循環計算中,你計算一個4x4數列的導數,結算始終是相同的,每次從新計算循環迭代,實際是在浪費計算機資源。相反,在循環流程的外部存儲導數的結果,並讓你的內部循環語句引用緩存結果,能夠極大地提高效率。我之因此喜好緩存和批處理,是由於他們可以改善全部你可以想到的性能問題,包括咱們在本專題中提到的問題,這是兩個很是有效的技術。若是你想成爲一名性能專家,你最好可以熟練掌握這兩項強大的技術。
目標: 在這個例子中,您將使用Traceview工具來查找並肯定哪些是阻礙應用程序性能問題的代碼。而後,您將應用批處理或緩存來優化性能不佳的代碼。
1)觀察: 首先,讓咱們看看咱們可以用怎樣一個給定的應用功能來觀察一個問題。在設備上啓動compute應用,而後啓動Batching/CachingActivity。你會看到一個正在舞蹈的海盜動畫。
上面有一個寫着"計算一些Fibonacci數"的按鈕,點擊它。你觀察到了什麼,爲何會這樣?不難看出,咱們的海盜朋友中止了搖擺。確定是按下按鈕以後才致使了這個現象,如今,你已經觀察到了一個問題,咱們來分析下。
2)Profile: 您剛剛瞭解了Traceview,讓咱們把它運用起來。點擊按鈕,觀察程序的運行軌跡。
3)分析: 根據運行軌跡分析,找出致使問題的方法是誰?
在這裏,咱們要找的答案,其實是computeFibonacci方法。讓咱們看看,我是怎麼樣找到它的。在下面的活動上運行TraceView,當我按下這個computeFibonacci函數,將會剖析這個函數。這是Traceview的輸出,這是運行Traceview時看到的輸出,你應該看到一些相似的內容,請注意這個大的粉色區域,這很糟,基本上,這表示有些函數在咱們的主線程上佔用大量的CPU時間。若是你按照獨佔CPU時間排序或者將鼠標懸停在這個粉色區域。你會發現computeFibonacci方法,它來自於咱們的緩存活動,是佔用CPU資源最多的函數,咱們須要解決這個問題。
有哪些好方法能夠解決這個問題呢?請選擇全部正確答案。咱們不該該爲主線程增長任何沒必要要的額外的工做,咱們應該讓線程僅處理用戶輸入和屏幕繪製.咱們看看是否可以優化函數,使用共享技術提升運行速度和減小資源開銷,讓咱們緩存這些中間值。
讓咱們來看看代碼,看看發生了什麼。咱們在主線程收到一個onClick()事件計算斐波那契。看看方法:
若是你熟悉算法理論,你能夠看到,斐波那契數遞歸執行,從代碼運行的角度來看,它是很是耗時的。解決這個問題的方法之一是經過計算和緩存中間結果。
爲了確保應用程序的高性能,每項功能都應該儘量高效地運行。可是這些功能的執行時間以及它們在代碼中所處的位置也很重要,當你首次啓動一個Android應用程序時,朱執行線程就已經建立了,主線程很是重要,由於它負責運行你的代碼,並在合適的視圖位置發送事件和執行繪圖功能。這些前面咱們已經講過,基本上來講,主線程是應用程序所在的線程,有時候,主線程也稱爲UI線程。例如,若是你觸摸屏幕上的按鈕,UI線程將會發送一個觸摸事件給視圖,視圖將按鈕狀態設定爲已按下設定,而後向事件隊列發送一個有效請求,而後UI線程處理此請求,並通知按鈕將其自己繪製爲已按下狀態。若是你有任何觸摸事件的處理代碼塊,將會在線程中執行,這些觸摸處理所用的時間越長,線程的執行時間就會越長,在繪圖功能執行完以前,視圖將會更新顯示狀態,讓用戶可以看到其狀態,這裏須要記住的是,輸入處理代碼與渲染和更新代碼,共享這個線程的處理週期時間。
這意味着,在觸摸事件處理,網絡訪問或數據庫查詢等計算週期時間,UI不會更新繪圖,在簡單的狀況下,渲染週期可能會延誤16毫秒左右,而讓用戶感到延遲。可是,若是你暫停UI線程渲染超過5秒,用戶將會看到"應用程序未響應"對話框,並詢問用戶是否會想要關閉你的應用程序,這樣可能致使用戶中止使用。那你如何解決這個問題,你要找出不須要在主線程上執行的功能,也就是說,不須要等它們完成以後,才能執行繪圖。你應該將這個功能轉移到一個單獨的獨立線程,這個線程不會阻止UI線程。例如,若是你按一下提交按鈕,以完成一個訂單,而後編寫和發送確認郵件,這能夠在單獨的線程上完成。Android有系列很好用的API,可以簡化這些工做。
我將要演示如何操做traceview,這個程序,能夠識別全部安裝程序的幀速率,操做方法以下,安裝後,點擊show on click handler按鍵,各位會看到很眼熟的跳舞海盜,而後點選display an image按鈕,如今你們會看到海盜不動了,但若是再這樣作,海盜又繼續跳舞了,同時Android圖示也會出如今海盜下方。跟以前同樣,只要點擊這個按鈕,海盜就不動了,下面要怎樣操做,想必你們都知道了,就是要利用這些工具,如今演示怎樣操做traceview。 在這裏,查看下剛纔追蹤的數據,結果出現冗長的數據,這是什麼意思呢?檢查數據,找出哪兩個方法調用得最頻繁。辨別哪些程序,佔用了大量的CPU資源?
咱們來看一下Traceview輸出,請注意這個大的活動區域,咱們來探討一些觀察到的信息,你會發現,最上方的這個函數,在排序時佔用的資源最多。還有其餘一些函數,例如這個nativePullOnce,可是它們是系統調用,咱們沒法控制它們。若是咱們更進一步,就會注意到這個nativeSetPixel和這個nativeGetPixel。讓咱們看看它們來自於哪裏,咱們展開它,看到了!!在繁忙的UI線程活動的某個位置,setPixel被調用,它來自於咱們的應用代碼,getPixel也是如此,彷佛來自於繁忙的UI線程活動。如今,咱們發現setPixel和getPixel,來自於繁忙的UI線程活動,讓咱們來更深刻地探討。
所以getPixel和setPixel並非咱們編寫的代碼,在這個代碼中哪一個父級方法調用getPixel和setPixel?父級方法其實是sepiaAndDisplayImage,讓咱們來看Traceview,來了解其含義。咱們回到跟蹤視圖,若是咱們展開可收縮菜單,咱們能夠找到父級調用堆棧,看到setPixel實際上被sepiaAndDislayImage方法調用。它在咱們繁忙的UI線程活動以內,讓咱們看看如何優化。
對圖像進行編碼處理是一個至關大的任務,這也是咱們不能輕易地優化掉的任務。特別是那些涉及網絡接入,冗長的數據庫調用和圖像處理,那麼通常規則是將它們從主線程移除。 讓咱們來使用異步任務:
前面咱們講過,一些類型的硬件可能會形成程序執行速度較慢,還記得那個浮點分支問題嗎?對於今天的硬件來講,這已經不是問題。可是有一些問題仍是須要引發注意,好比說,你所使用的編程語言的基本元素的效率,以排序等基本算法爲例,如今,有不少的排序算法,對於不一樣的狀況,它們各有優劣,例如,當元素數量少於一千或在大型已排序列表中尋找一個對象時,快速排序法一般比起冒泡排序法更快。通常狀況下,最好的方法是二分查找算法,可是,當在未排數組中尋找對象時狀況變得徹底不一樣,不一樣於比較每個對象以查找你想要的值。你可使用一個哈希函數來當即找到它,這是現代計算機科學和數據結構方面的基本知識。
幸運的是,現代編程語言像Java等,爲你提供了這些容器和算法,所以你再也不須要本身反覆地編寫Murmur3哈希函數和快速排序算法。可是你須要知道另一些事情,在我多年的編程生涯中,一個常常會影響項目性能的問題,是因爲這些語言提供的容器對象的性能所引發的。這聽起來難以想象!Java提供一個矢量類的實現,你能夠任意push、pop,添加和取消對象,爲了得到這種靈活性,它在內部使用鏈式列表結構,這種結構具備一系列獨特的性能特性,在你操做這種列表時,它的速度超級快,可是,當你在其餘位置進行插入或刪除時,它會消耗大量的時間。我要說的是,底層系統提供的這些容器並不會考慮,你的程序將會如何實際使用它們,James Sutherland發表了一系列的基準測試報告,他認爲,咱們須要注意性能與功能之間的一些差別。例如,他發現Hashtable比HashMap大約快22%,具體視你如何使用這些容器而有所不一樣,咱們須要思考的是,你是否曾經分析過你在代碼中使用的容器類。你是否堅信,你在代碼中使用的容器的實際運行速度絕對是最快的。一個好消息是,你可使用Android中的MPI來剖析這些容器的性能。
在這個例子中,你將會看到,建立應用時,容器中不恰當的數據結構所形成的性能問題,爲此咱們可使用Android SDK中的工具,來識別不恰當數據結構帶來的性能問題。存儲與修改應用程序數據代碼的性能問題,很容易被開發人員忽悠,因此讓咱們來探討這個問題,並從性能角度來解決。對於這個示例,咱們將重點關注方法的執行時間,這個方法生成一個數字列表,並按它們的受喜好程度排名,爲了便於演示,當咱們按下這個按鈕,也就是dump popular numbers to log按鈕,將會調用這個方法,與前面的示例類似。這段代碼會形成圖像短暫停頓從而影響跳舞海盜圖像的幀率。若是你看一下logcat,你會發現,它經過tag popularity dump運行,像這樣。讓咱們看看底層發生了什麼,並學習如何測量這段代碼的運行速度,咱們想要仔細弄清楚,當咱們點擊這個按鈕時發生了什麼,所以咱們使用trace類beginsection和endsection方法來指定開始測量位置和結束位置。首先,讓咱們找到計算受喜好程度的代碼,咱們將使用它計量代碼效率和運行時間。
問題: beginSection與endSection應該放在哪一個地方?
在這個例子中,你會看到咱們是如何選擇一個更有效的數據結構,提升了數據訪問時間。
問題: 經過使用dumpPopularRandomNumbersByRank(),咱們粗略招致排序操做的成本,再加上o經過雙循環迭代來生成排名列表(N^ 2)成本。
改進注意事項: 總有一個項排序一次性成本(你會根據本身的數據集的大小挑選理想的排序項)。 經過使用一個HashMap中,咱們經過得到直查詢時間(爲O(n))與二次時間爲O(n^2)未優化的狀況下,陣列的訪問節省時間。咱們節省的訪問時間這一個訂單,由於數據已經存儲在鍵值對!
這顯著事項當n(或樣品尺寸)特別大,這多是若是你用,好比工做,在世界上全部的職業足球運動員名單,想碰到一些屬性顯示它們的排名狀況。
讓咱們花點時間確認的改善。來吧,在加回(若是你刪除它)調用endSection和beginSection。而後,運行在優化代碼systrace。
關注個人技術公衆號"程序員驛站",天天都有優質技術文章推送,微信掃一掃下方二維碼便可關注:
![]()