【騰訊Bugly乾貨分享】微信讀書iOS性能優化

本文來自於騰訊bugly開發者社區,非經做者贊成,請勿轉載,原文地址:http://dev.qq.com/topic/578c93ca9644bd524bfcabe8git

「8小時內拼工做,8小時外拼成長」這是你們共同的理想。除了天天忙於工做外,咱們都但願能更多地區吸取領域內的新知識與新技能,從而走向人生巔峯。github

Dev Club 是一個交流移動開發技術,結交朋友,擴展人脈的社羣,成員都是通過審覈的移動開發工程師。每週都會舉行嘉賓分享,話題討論等活動。sql

上一期咱們邀請了騰訊SNG工程師「王少鳴」分享了《React Native項目實戰總結》數據庫

本期,咱們邀請了騰訊WXG iOS開發工程師「姚海波」爲你們分享《微信讀書iOS性能優化》

如何加入 Dev Club?緩存

移動端開發經驗 >= 2 年,微信掃描下方羣管理微信二維碼,備註姓名-公司(或產品) 申請加入。安全

外部羣二維碼


分享內容簡介:
微信讀書做爲一款閱讀類的新產品,目前還處於快速迭代,不斷嘗試的過程當中,性能問題也在業務的不斷累積中逐漸體現出來。最近的1.3.0版本發佈後,關於性能問題的用戶反饋逐漸增多,爲此,團隊開始作一些針對性的性能問題優化。本次分享主要介紹咱們發現問題、解決問題和預防問題的歷程。性能優化

內容的大致框架:微信

  1. 如何發現性能問題
  2. 性能問題的解決方法
  3. 如何預防性能問題
  4. 優化成果
  5. 總結

分享人介紹:markdown

姚海波 廣州研發部 iOS開發工程師。網絡

負責過的產品:QQ郵箱iOS客戶端,目前主要負責微信讀書iOS客戶端的開發。


下面是本期分享內容整理


你們晚上好,我是來自廣研的姚海波,你們能夠叫我hypo。目前是微信讀書項目中的iOS開發,主要負責閱讀器相關的模塊,還有APP總體性能優化方面的工做。

今天分享的內容是關於微信讀書iOS開發過程當中,咱們解決性能問題的基本思路和方法,包括發現問題解決問題預防問題三個方面。

1、發現問題

首先,根據我的的開發經驗,我不得不認可,當應用發展到必定程度後,性能問題就不可能徹底避免。以往咱們老是但願能尋找一種解決性能問題的一勞永逸的方法,實際上是不太現實的。因此咱們換個思路,如何儘早的發現性能問題,而後解決問題。

在發現問題方面,咱們項目也並無什麼高招,主要有兩個方面

  1. 用戶反饋(包括測試人員)
    受限於測試時間和用戶反饋的積極性,性能問題每每到了比較嚴重的程度,開發人員才真正發現問題。

  2. 在線監控
    在線監控主要有業務性能監控卡頓監控

業務性能監控,主要在咱們認爲很是關鍵的操做路徑,例如:

卡頓監控,是用了Bugly的工具,而後經過動態下發開關,用抽樣的方法進行上報

還有一些反饋卡頓的用戶,咱們也會經過這個方法來查找問題

2、解決問題

而後,在解決性能問題方法,相信你們都累積了不少經驗。

產生性能問題的緣由多種多樣,因此解決的辦法也不盡相同,各類奇技淫巧都有可能派上用場,這裏我大概介紹一下咱們項目中用到的一些方面:

1. 優化業務流程
2. 合理的線程分配
3. 預處理和延時加載
4. 緩存
5. 使用正確的API

1. 優化業務流程

性能優化看似高深,真正落到實處纔會發現,最大的坑每每都隱藏在於業務不斷累積和頻繁變動之處。優化業務流程就是在知足需求的同時,提出更加高效優雅的解決方案,從根本上解決問題。從實踐來看,這種方法解決問題是最完全的,但一般也是難度最大的。

這是咱們其中一個業務優化的案例

看似挺簡單的優化,但真正落到實處,纔會出現其中的坑有多大,因此重構優化的時候,還得有顆堅強的心!

2.合理的線程分配

因爲GCD實在太方便了,若是不加控制,大部分須要拋到子線程操做都會被直接加到global隊列,這樣會致使兩個問題:

  1. 開的子線程愈來愈多,線程的開銷逐漸明顯,由於開啓線程須要佔用必定的內存空間(默認的狀況下,主線程佔1M,子線程佔用512KB)。
  2. 多線程狀況下,網絡回調的時序問題,致使數據處理錯亂,並且不容易發現。爲此,咱們項目定了一些基本原則:
  • UI操做和DataSource的操做必定在主線程。
  • DB操做、日誌記錄、網絡回調都在各自的固定線程。
  • 不一樣業務,能夠經過建立隊列保證數據一致性。例如,想法列表的數據加載、書籍章節下載、書架加載等。

合理的線程分配,最終目的就是保證主線程儘可能少的處理非UI操做,同時控制整個App的子線程數量在合理的範圍內。

3.預處理和延時加載。

預處理,是將初次顯示須要耗費大量線程時間的操做,提早放到後臺線程進行計算,再將結果數據拿來顯示。

延時加載,是指首先加載當前必須的可視內容,在稍後一段時間內或特定事件時,再觸發其餘內容的加載。這種方式能夠頗有效的提高界面繪製速度,使體驗更加流暢。(UITableView就是最典型的例子)

這兩種方法都是在資源比較緊張的狀況下,優先處理立刻要用到的數據,同時儘量提早加載即將要用到的數據。在微信讀書中閱讀的排版是優先級最高的,所在在閱讀過程當中會預處理下一頁、下一章的排版,同時可能會延時加載閱讀相關的其它數據(如想法、劃線、書籤等)。

4.緩存

cache多是全部性能優化中最經常使用的手段,但也是咱們極不推薦的手段。cache創建的成本低,見效快,可是帶來維護的成本卻很高。若是必定要用,也請謹慎使用,並注意如下幾點:

  • 併發訪問cache時,數據一致性問題。
  • cache線程安全問題,防止一邊修改一邊遍歷的crash。
  • cache查找時性能問題。
  • cache的釋放與重建,避免佔用空間無限擴大,同時釋放的粒度也要依實際需求而定。

5.使用正確的API

  • 選擇合適的容器;
  • 瞭解imageNamedimageWithContentsOfFile的差別(imageNamed適用於會重複加載的小圖片,由於系統會自動緩存加載的圖片;imageWithContentsOfFile僅加載圖片)
  • 緩存NSDateFormatter的結果。
  • 尋找(NSDate *)dateFromString:(NSString )string的替換品
  • 不要隨意使用NSLog();

這方面主要仍是靠經驗的累積

上面只是列舉了幾種常規手段,相信你們在實踐過程當中,確定還有不少的高招。

3、預防問題

通過一段時間的性能優化工做,咱們團隊達成了一項共識,與其花那麼時間去發現問題,查問題,還不如多開發一些工具,讓問題儘可能暴露在開發階段,最好達到避免共性問題。因此,咱們老是想開發一些有意思小工具來作這種事情。

下面列舉幾個咱們認識還挺有幫忙的工具:

1.內存泄露檢測工具。
2.FPS/SQL性能監測工具條
3.UI/DataSource主線程檢測工具
4.排版引擎自動化檢測工具
5.書源檢測工具

1.內存泄漏檢測工具

MLeakFinder,這個已經開源了,是咱們團隊中zeposhe的傑做。

在此以前,內存泄露引發的性能問題是很難被察覺的,只有泄露到了至關嚴重的程度,而後經過Instrument工具,不斷嘗試才得以定位。MLeakFinder能在開發階段,把內存泄露問題暴露無遺,減小了不少潛在的性能問題。

2.FPS/SQL性能監測工具條。

工具條是在DEBUG模式下,以浮窗的形式,實時展現當前可能存在問題的FPS次數和執行時間較長的SQL語句個數,是團隊成員tower開發的。

FPS監測的原理並不複雜,雖然不是百分百準確,但很是實用,由於能夠隨時查看FPS低於某個閾值時的堆棧信息,再結合當時的使用場景,開發人員使用起來很是便利,能夠很快定位到引發卡頓的場景和緣由。SQL語句的監測也很是實用,對於微信讀書,DB的讀寫速度是影響性能的瓶頸之一。所以在DEBUG階段,咱們監測了每一條SQL語句的執行速度,一旦執行時間超出某個閾值,就會表如今工具條的數字上,點擊後能夠進一步查詢到具體的SQL操做以及實際耗時。

頂部工具條點擊後,就能夠查到具體是哪條sql語句慢

這個工具幫助咱們在開發階段發現了不少卡頓問題,尤爲是一些不合理的SQL語句,例如:
在想法圏的優化過程當中,利用這個工具,咱們就發現想法圈第一次加載更多,執行的SQL語句耗時居然達到了1000多毫秒。

_SELECT * FROM WRReview INNER JOIN WRUser ON WRReview.fromId = WRUser.vid WHERE WRReview.type & ? AND WRReview.createTime <= ? ORDER BY WRReview.createTime DESC , WRReview.itemId ASC  LIMIT ?_

經過explain,能夠發現這條SQL效率之低:

SEARCH TABLE WRReview
SEARCH TABLE WRUser USING INTEGER PRIMARY KEY (rowid=?)
USE TEMP B-TREE FOR ORDER BY
  • 沒有創建合適的索引,致使WRReview全表掃描。
  • 排序字段沒有索引,致使SQLite須要再一次B-TREE排序。
  • 兩字段排序,性能更低。

優化:給WRReview的 fromId createTime 兩個字段增長了索引,並去掉一個排序字段:

_SELECT * FROM WRReview INNER JOIN WRUser ON WRReview.fromId = WRUser.vid WHERE WRReview.type & ? ORDER BY WRReview.createTime DESC  LIMIT ?_

Explain的結果:

 

SCAN TABLE WRReview USING INDEX WRReview_createTime
SEARCH TABLE WRUser USING INTEGER PRIMARY KEY (rowid=?)

SQL執行時間直接降了一個數量級,到100毫秒左右。

 

3.UI/DataSource主線程檢測工具。

該工具是爲了保證全部的UI的操做和DataSource操做必定是在主線程進行。實現原理是經過hook UIView的setNeedsLayoutsetNeedsDisplaysetNeedsDisplayInRect三個方法,確保它們都是在主線程執行。子線程操做UI可能會引發什麼問題,蘋果說得並不清楚,實際開發中咱們遇到幾種神奇的問題彷佛都是跟這個有關。

  • app忽然丟動畫,彷佛iOS系統也有這個bug。雖然沒有確切的證據,但使用這個工具,改完全部的問題後,bug也好了(不止一次是這樣)。

  • UI操做偶爾響應特別慢,從代碼看沒有任何耗時操做,只是簡單的push某個controller。

  • 莫名的crash,這固然是由於UI操做非線程安全引發的。

更多時候,子線程操做UI也並不必定會發生什麼問題,也正由於不知道會發生什麼,因此更須要咱們警戒,這個工具替咱們掃除了這些隱患。雖然,蘋果表示,如今部分的UI操做也已是線程安全了,但畢竟大部分還不是。DataSource的監測是由於咱們業務定下的原則,保證列表DataSource的線程安全。

4.排版引擎自動化檢測工具

排版引擎是微信讀書最核心的功能,排版引擎檢測工具本來是爲了檢驗排版引擎改進過程當中準確性,防止由於業務變動,而影響原來的排版特性。實現原理是結合自動化腳本和App自己的排版引擎,給書庫中的每一本書創建一個鏡像,鏡像的內容包括書籍的每一章每一頁的截圖。而後分析同一頁碼的兩個不一樣版本的圖片差別,就能夠知道不一樣版本的排版引擎渲染效果。可是我發現,只要稍加改進,排版後記錄每一個章節排版耗時,就能夠知道每一個版本變化後同一個章節的耗時變化,以此做爲排版引擎的性能指標。這個工具保證了微信讀書,即便在快速迭代過程當中也不會丟失閱讀的核心體驗。雖然這個工具沒法在其它項目中複用,可是提醒了咱們,能夠經過自動化工具來保證產品最核心功能的體驗。

這個雖然業務相關性比較強,可是對於某些應用的自動化測試也是有效的

5.書源檢測工具

微信讀書爲了支持正版版權,目前書源徹底依賴於後臺,不容許本地導入。書源的優劣的直接影響排版的效果和性能。爲了解決了部分書籍沒法打開或者亂碼的問題,咱們藉助了後臺同窗的書源檢測工具。對線上全部epub書籍(大概13,000本)進行掃描,按照章節大小進行排序。對於章節內容特別大的書籍重點檢測,從新排版,解決了一批epub書籍沒法打開的問題。同時針對章節內容亂碼的問題,對全部txt的書籍進行了一次全量掃描,發現了一些問題,但還沒法準確找出全部亂碼的章節,這一點還在努力改善中。

4、優化成果

  1. 總體使用感覺上,已經能夠明顯區分兩個版本的性能差別,這一點也能夠經過天天的用戶反饋數據中獲得驗證。1.3.0和1.3.1分別發佈一週後反饋的卡頓數從10個降到了3個,從整體反饋比例的2.8%降到0.8%。
  2. 某些關鍵業務,耗時也有明顯改善。
  3. 極端案例的修復。超大的epub書籍已經過後臺進行拆分,解決了沒法打開書籍的狀況。
  4. 針對低端機型,去掉了某些動畫,交互更加流暢。

5、總結

經過上述介紹,咱們能夠看出,性能問題廣泛存在,無可避免,與其花費大量時間,查找線上版本的性能問題,不如提升總體團隊成員性能優化意識,藉助性能查找工具,將性能問題儘早暴露在開發階段,達到預防爲主的效果。

問答環節

Q1:想問下大家 DB 操做這部分涉及到多線程讀寫是怎麼處理的?

咱們用了FMDB,它已經處理了這種狀況。

Q2:主線程檢測工具備開源嗎?

這個暫時沒有開源,可是會推進開源。

Q3:除了 sqlite 語句的優化以外,db 這部分還有沒有其餘方面的優化工做?

https://github.com/Zepo/GYDataCenter
咱們有一個本身的DB框架,是ORM的,作了不少優化的工做,最近剛開源,你們能夠看看。

Q4:請問大家選擇用sqlite的考量是什麼, 有沒有考慮過使用其餘的db如realm?

選擇sqlite是歷史緣由,由於咱們已經基於sqlite作了一個高性能的DB框架,並且也是通過QQMail App驗證的。realm有考慮過,可是由於不是開源,因此估計不用採用。

Q5:FMDB 的解決方案,我理解是放到一個隊列裏,雖然能夠解決多線程讀寫的問題,可是隊列的處理仍是會阻塞住來自不一樣線程的請求,對麼?

是的。咱們一直也是讀寫都在同一條隊列,其實並無太明顯的性能瓶頸, 由於在sqlite之上咱們還有一層基於model的cache。

Q6:合理的使用線程,多線程之間的同步這塊兒有什麼方案或建議?

這裏咱們也並無什麼通用的方案,原則是儘可能避免使用多線程。必定要用的時候,也是根據業務謹慎選擇。
或者咱們私下能夠再具體討論一些業務場景。

Q7:業務場景裏會不會涉及到有讀操做依賴寫操做完成的狀況,不然會出現讀操做的數據不許確的狀況。FMDB 感受不能很好的解決這個問題。

讀操做 依賴寫操做完成,這種場景必定會有的。可是這種問題應該是業務流程本身控制,而不是DB應該考慮的事情,DB性一能保證的就是按照業務提交的順序,順序執行。

Q8:能不能問下 微信讀書的數據庫的記錄 通常是在什麼級別,百、千?有沒有嘗試去作過一些壓測,數據量達到多少的時候會遇到瓶頸?

微信讀書的數據庫記錄並非很大,單表記錄最多可能也就10w的數據級別。QQ郵箱的mailApp跟咱們是用的同一套,可是數量級別遠大於微信讀書。目前發現的瓶頸是DB文件達到200M以上時,sqlite的性能會明顯受到影響,不過具體緣由還在調查中。有作過一些壓力測試,用來對比CoreData,可是具體數據我這裏暫時沒有。

Q9:卡頓監控這塊能詳細說說麼,用的是Bugly的哪一個工具呢,抽樣上報具體是怎麼樣的?

Bugly庫裏有這個接口能夠用+ (void)enableBlockMonitor:(BOOL)enable
另外再動態下發一個開關,設置這個值就行了。

Q10:微信讀書這麼成功,方便說下她的架構嗎?我以爲架構好纔是她可優化的第一步。

哈哈,如今還遠談不上成功啦。架構要用圖來畫才方便看,我暫時還沒總結整個app的架構, 能夠看看關於閱讀器epub渲染的一個架構。

Q11:sql對於版本升級時表結構發生變化時如何處理?特別是跨版本升級!

https://github.com/Zepo/GYDataCenter 這個是基本ORM的一個框架,會自動把model和sqlite表的字段作一個映射,升級的時候,若是發現sqlite缺乏的字段,會自動建立。可是,由於sqlite不能修改字段,因此咱們也只能用於新增字段。

Q12:大家的 db 是隻有一個文件,仍是嘗試分文件存儲的?

看業務需求,目前是多個DB文件。

更多精彩內容歡迎關注bugly的微信公衆帳號:

騰訊 Bugly是一款專爲移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的狀況以及解決方案。智能合併功能幫助開發同窗把天天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響用戶數最多的崩潰,精準定位功能幫助開發同窗定位到出問題的代碼行,實時上報能夠在發佈後快速的瞭解應用的質量狀況,適配最新的 iOS, Android 官方操做系統,鵝廠的工程師都在使用,快來加入咱們吧!

相關文章
相關標籤/搜索