iOS性能優化系列篇之“優化總體原則”

iOS性能優化系列篇之“優化總體原則”

轉載至:https://juejin.im/post/5ac216075188255c93237306

筆者由於在iOS開發過程中做過一些優化的工作,對iOS性能優化有一些粗淺的認識,一直想把自己這些經驗,簡單總結一下。於是最近在工作閒暇時間,準備針對iOS開發的性能優化寫一系列文章。

作爲整個系列的第一篇,我打算針對iOS的優化中的一些總體原則做一些總結。因爲我覺得無論列表流暢度優化也好、啓動時間優化也好還是說其他方面的優化,都有一些共性的原則,只有掌握了這些總體性的原則,才能夠更好的做優化,給我們具體的優化任務指明方向,讓我們少繞彎路。後面如果時間允許,我可能會寫一些關於列表流暢度、啓動時間和內存優化等方面的文章。

在第一篇“優化總體原則”裏面。我對優化總體原則總結出包括不要提前過度優化、要找到性能瓶頸、要在不同性能指標間權衡、要理解優化任務的底層運行機制和要有技術保障體系五大原則,其中具體闡述每一個原則的時候並不侷限於性能優化方面,會發散到其他的相關領域,會對一些延伸的領域做一些簡單的探討,希望能夠對讀者有一些啓示。以下是第一篇的主要內容。

一、不要提前過度優化

這個原則包括優化的過程中需要避免的兩個陷阱,即提前優化和過度優化。

  • 提前優化指的是在開發的起始階段就把性能優化作爲一個重要的任務來考慮,在沒有實際數據指標的基礎上,爲了性能提前做的些盲目優化工作。

    當然這個觀點可能會引起爭議,因爲在某些開發領域,一些性能指標以歷史的經驗來說,的確有很大概率甚至必然會有性能瓶頸的問題,因此在架構初期就需要考慮性能的問題。 因此如果把“不要提前優化“這個觀點推廣所有開發領域上的話,我認爲可能不一定合適。但是如果把此觀點約束在iOS開發這一領域內,我個人認爲還是成立的。因爲在目前階段iOS平臺設備性能普遍較好,蘋果無論是硬件層面還是系統層面對性能方面都做了大量的優化。所以我認爲性能方面並不是iOS開發過程中需要首要考慮的因素。相比性能, 我個人認爲在iOS開發的初始階段,以下幾個方面是更重要的,是需要首先考慮的。

    首先需要考慮的是架構的選擇,這裏的架構指的是Native架構、web架構、Native和web混合架構和跨平臺的架構。這裏面我個人的意見是首先應該儘量避免使用web架構,從Facebook早期的失敗經驗可以看出,web和Native相比的確存在諸多性能、體驗等方面的問題。連大廠都無法徹底改善webview的問題,何況我們。但是在一些和用戶體驗相比,對動態化需求更加迫切的應用場景下,是可以選擇web架構的,比如大家都一直在吐槽某鐵路售票軟件。Native架構的優勢是產品體驗好,對大多數iOS開發者技術棧友好,缺點是由於蘋果對熱更新做了嚴格限制,導致一些動態化的方案無法使用。Native和web混合架構主要是在Native架構上,在一些運營需求十分強烈的場景下(如電商等場景),某些模塊使用web開發,這樣既可以在App大部分場景下使用Native架構,保證用戶體驗,又滿足了部分場景動態化的需求。跨平臺的架構主要是可以減少多端開發的成本,使用一套代碼完成iOS和Android兩個平臺的開發,目前主流的框架有ReactNative、Weex和Xamarin等。這些跨平臺架構的願景都很美好,但實際使用過程中,個人覺得現階段並不比使用Native架構節省人力,其中會遇到許多已知的未知的坑,當然作爲新的技術我們應該持開發的心態,但在使用時候也需要全面的評估,尤其作爲一個可能有很長生命週期的應用,在使用非官方推薦的開發架構也好、開源庫也好,如果後期無人維護的話,自己團隊是不是有實力去接盤,如果不能,使用蘋果官方推薦的技術棧則更穩妥些。

    其次需要考慮的是開發語言的選擇,這個方面其實在選擇了架構之後,也將可選的開發語言範圍縮小到幾個。而且實際項目開發中並不難選,因爲團隊開發人員的技術棧幾乎決定了使用的開發語言,如果使用了大部分團隊成員都不熟悉的語言,我相信即使所選語言在很多方面都有壓倒性的優勢,項目的推進也不會十分順利。但是排除團隊技術棧的因素,不同的開發語言的確是各有千秋。大家都知道iOS開發過程中,如果使用Native架構,官方的開發語言是objc和swift。objc是早期iOS開發的官方推薦語言,優點是其動態性,十分靈活,可以實現許多“黑魔法”,缺點語法略怪異(當然對於iOS開發者,使用久了也不覺得怪了),另外是對一些高級的語言特性支持的不是很好(記得最初使用objc開發iOS應用的時候,因爲一些特殊的需求。由於objc不支持namespace,給團隊造成了很大的困擾)。swift是蘋果近年來主推的開發語言, 其吸收了許多其他語言的先進特性,也比較容易上手。關於兩種開發語言的具體技術細節,大家有興趣可以自己查看一些資料瞭解下。雖然蘋果一直在力推swift,但是目前在國內iOS開發領域,由於一些用戶基數大的主流App,均是在swift出現前使用objc編寫的,而且大多經過了數年的版本迭代,加之早起swift ABI的不穩定和版本之間升級需要較多工作,還有swift和objc混編的一些問題,導致目前國內主流App大多仍使用objc作爲開發語言。在一些創業公司,或者新的項目中,纔有部分開發者使用swift,當然如果目光放長遠的話,未來一定是swift的天下,這兩年objc在每年的語言排名中逐年下降也側面印證了這一點。 除了官方推薦的objc和是swift之外,如果使用跨平臺等其他架構,還可以使用如js、c#等語言,有興趣的可以自行了解下。

    再次需要考慮的是開發過程中具體的代碼架構的選擇,這裏只簡單談談Native架構下的代碼架構選擇。 目前iOS開發中常用的架構有MVC 、MVVM、VIPER、MVP等。關於這些架構,網上目前有很多的介紹,大家如果對具體細節有興趣可以自行查閱。這裏我只想補充一點,大家在學習和實踐時,不要盲目跟風新技術,比如MVVM等架構未必比MVC好很多,MVC也未必是一個過時的框架。要知道很多新架構帶來的擴展性和解耦行都是通過引入間接層來實現的,隨之而來的可能是更多的膠水代碼和更復雜的代碼結構。希望大家在選擇的時候能夠根據項目的特點和團隊自身的狀況,選擇最適合自己團隊和項目的代碼架構。

    除了上面說的三點,還有一些其他的關鍵點需要大家在項目初期考慮,比如如何在團隊內部達成統一的代碼風格?一些關鍵的技術如何選型?如何保證代碼結構清晰、簡單、擴展性好等等。性能問題可以在項目後期開始考慮,如果真的發現明顯的性能問題再優化也來得及。比如項目一開始憑直覺感覺某一個模塊可能會有性能問題,就盲目使用多線程,而不是根據實際情況具體問題具體分析。會導致程序複雜且容易出現線程安全問題。

  • 過度優化是指爲了優化性能,過度增加系統複雜度和維護成本,使得開發週期變長。雖然可能性能上帶來了一定的提升,但是和過度優化而導致的這些缺點來比,這麼做顯而易見是得不償失的。

    筆者在工作過程中,發現許多同學在性能優化過程中,都容易陷入這種過度優化的陷阱。比如這一個簡單的設置界面,一共只有十幾個靜態的cell, 如果去考慮圓角性能、離屏渲染、圖片緩存、高度緩存、異步渲染甚至緩存佈局信息,這些無疑是陷入了過度優化的陷阱,在這個應用背景下,簡單快速的實現功能纔是第一要務。在目前蘋果的開發框架和平臺上,一般如果出現性能問題,以我的實際經驗來說,問題大部分是出在業務邏輯上面,所以遇到問題首先需要在業務邏輯上找問題,一些過度的極限的優化,完全是沒有必要的。

    其實不光是性能優化,我發現許多同學在日常開發中,處處都有過度設計的情況。比如設計模式中的design happy這一陷阱,許多初學者在剛開始學習設計模式的時候,十分癡迷設計模式在解決不同問題時,對代碼的解耦性和可擴展性上的威力,在開發過程中會時時刻刻想着應該用什麼設計模式。結果導致很多的過度設計,其實我們寫代碼過程中,如果能遵守基本的SOLID原則,大部分情況下就可以寫出高質量的代碼。

    另外一個例子是組件化。近期iOS組件化是一個十分流行的話題,有許多團隊提出了不同的組件化方案。實際項目中,團隊在是否採用組件化方式開發的選擇上,我希望要結合項目特點和團隊組織架構形式具體問題具體分析,不要盲目跟風。在產品功能相對單一、開發人員較少、並行開發需求不強烈的情況下,推行組件化,不但增加系統複雜度,而且增加開發人員學習成本高,使得開發成本變大,我個人覺得這種規模的應用初期需要更多考慮的是如何快速上線、快速迭代和保證App質量。因此如果能夠進行清晰的分層,嚴格遵守簡單統一的架構模式即可。組件化比較適合從功能形態上可以清晰劃分若干模塊的產品,比如美團、58同城、淘寶和攜程等產品,內部有多個業務模塊,而且這些公司開發此類“航母”App的時候,會從組織架構把不同業務劃分給不同的開發團隊,爲了能夠保證不同團隊之間能夠獨立並行開發和發版,最大程度上減少代碼的依賴程度,這個時候應用組件化則是最佳實踐。

    上面是對不要提前過度優化原則的詳細闡述,並引申到相關開發領域,做了一些不成熟的探討。下面介紹性能優化總體原則的第二個。

二、要找到性能瓶頸

在做優化前,一定要首先找到性能瓶頸有哪些,依性能嚴重程度逐個解決。不要盲目優化,否則最後可能花了很大的力氣,優化掉的可能只是性能損耗很小的一部分。這一原則我覺得尤爲重要,因爲我在工作中遇見過包括我在內,很多不進行性能瓶頸查找,全憑主觀猜測進行性能優化的情況。 在尋找性能瓶頸過程中,也需要注意以下問題。

  • 不要主觀猜測,讓性能評測數據說話。

    這一點十分重要,要時刻記住要以事實說話,不要以爲某個函數使用的算法的時間複雜度是O(n2)就覺得一定會有性能問題,非要費很大力氣優化到O(n*logn), 殊不知你的輸入數據可能只有幾十或者幾百個,即使O(n2)也不會有多大的性能問題。也不要以爲某個方法僅僅調用了系統庫的一個簡單get方法,就不會有什麼性能問題,殊不知這個get方法裏可能包含一些十分耗時的操作(比如磁盤IO)。因此在遇到性能問題的時候,一定不要憑主觀猜測,實地跑一下性能數據,讓數據告訴我們性能瓶頸究竟在哪裏。

  • 要使用恰當的性能評測工具。

    對於開發版本的性能優化,Xcode提供的instruments絕對是最好的尋找性能瓶頸的工具,沒有之一。instruments有豐富的性能評測工具,包括常用的Core Animation、Time Profiler、Leaks和Allocations等等。這些工具在分析函數執行時間、fps和內存等方面給我提供了十分便捷的功能。在使用instruments過程中需要注意:

    1. 要使用真機,而不是模擬器。模擬器的CPU比iOS機器要快很多,所以在模擬器上,CPU相關的操作會更快。因爲Mac的GPU和iOS設備上的GPU不同,所以模擬器需要在CPU上通過軟件去模擬iOS設備上的GPU,所以GPU相關的操作會更慢。因此如果使用模擬器去進行性能優化的話,評測設備和真實用戶設備性能表現的不一致,會導致優化的效果大打折扣。這裏面內存是一個例外,在做內存優化的時候,使用模擬器和真機一般差別不大,可以使用模擬器進行內存的優化。

    2. 要使用Release配置而不是Debug配置,因爲在release包的時候,編譯器會做一些優化以提高性能。自己的工程代碼可能也會在release下做一些優化,比如去除log信息和一些debug功能等。我們關心的是release下的性能,因爲用戶最終也是使用的release的安裝包。所以測試的時候要一定要記住在release配置下進行,instruments進行性能評測的時候,默認是在release下進行的。但是工程代碼裏面的優化則需要自己注意。筆者就曾經爲此付出過很大代價,因爲沒有注意工程代碼裏面的一些debug功能,導致優化過程中錯誤的認爲動態庫是影響啓動時間的罪魁禍首,花了很大力氣把動態庫修改爲靜態庫,白白浪費了很多時間。

    3. 要使用性能相對差的機器進行評測,因爲我們需要保證的是我們的應用在性能差的機器上也有良好的表現。如果有條件,最好能夠覆蓋多個機型,和我們傳統上的認識不同,機型越新性能不一定越高,例如iPad3在動畫和渲染性能上比iPad2差。

    4. 要覆蓋不同系統版本,因爲在iOS系統上,一般系統版本越高,同一機器性能大體上趨於差,所以如果只覆蓋低版本的機型,可能在高版本上表現的性能會不盡如人意。

    除了使用instruments,還可以使用log等方式進行查找性能瓶頸。

    對於線上的App,查找性能瓶頸的工具主要是Application Performance Management(APM)。目前各大公司基本都有自己的APM,主要是對線上App進行性能監控以及預警。因爲在開發和測試階段,由於使用人數相對比較有限,很難覆蓋所有的業務場景。而在App發佈出去後,大量用戶在使用過程中的性能表現,會給我們的App帶來更全面的性能評測數據,因此線上App的性能監控是十分重要的。

  • 要抓重點,有的放矢。找到性能損耗大的前N個問題,依重要程度和解決的難易程度解決,這樣才能花最少的精力,解決最大問題。

三、要在不同性能指標間權衡,達到總體最優

在已經找到性能瓶頸的時候,解決性能問題的方法則需要具體問題具體分析,要在不同性能指標間權衡,以達到總體最優。

這需要我們要有整體的意識,App的性能可以分爲很多類,不同的性能指標對用戶體驗造成的影響也不盡相同,比如fps主要影響的是用戶的滑動體驗,頁面加載時間和應用啓動時間影響的是用戶等待時間上的體驗。我們在優化的過程中,要牢記我們的目標是希望App的整體體驗最優,而不是某一單項的性能指標最優。不同優化指標之間可能是呈正相關,比如優化了滑動過程中大量函數的耗時時間,使得fps性能提升,可能會導致App的耗電量變少。同時,不同優化指標也可能是負相關、相互制約的,比如爲了流暢性做了過多的cache,會導致內存性能下降,甚至導致因爲memory warning導致被系統kill掉,這無疑對App的整體體驗造成了負面的影響。因此實際優化過程中需要我們反覆權衡利弊和取捨,達到整體的性能最優。

四、要理解優化任務的底層運行機制

如果不理解優化任務的底層運行機制,可能很難達到更好的優化效果。

比如在做啓動時間優化的時候,如果你不知道iOS中App的啓動時間是由main之前和main之後兩部分時間組成的,此時如果你的App是因爲main函數之前的部分佔用了過多的啓動時間,可能你花了大量的精力去優化main之後的時間卻沒有達到好的優化效果。如果你不知道App啓動過程的運行機制,就無法知道去檢查是否鏈接了過多的自定義的動態庫或者去load函數裏面確認是否有耗時的操作等等。還有在做fps優化的時候,如果不瞭解卡頓的底層原因是什麼、一個view從創建到顯示過程中經歷那些步驟、CPU和GPU在這個過程中都扮演什麼角色,則很難做到絲滑般的順暢?還有在做內存優化的時候,如果不瞭解內存分爲哪幾類、系統對App和不同類型extension的內存限制機制的不同、超過限制系統會採取什麼操作等等,也很難把內存優化做好。因此只有深入瞭解底層機制才能更好的有針對性的提出更優的解決方案。

其實不只是在性能優化方面,在開發過程中很多情況下,瞭解底層的原理會讓你變得更高效,更容易解決遇到的各種問題。在這裏分享一個我印象比較深的一次經歷。記得有一次在開發某個功能的時候,需要用到level db數據庫,在開發過程中做單元測試的時候發現,level db的本地存儲文件在不斷刪除和寫入的過程中,越變越大,甚至達到1G大小。當時第一印象以爲是在使用上出了問題,所以上層業務邏輯上查找問題,結果查了很久都沒有找到問題。但如果我在使用level db的時候去多瞭解一下其底層的實現原理,瞭解LSM(Log-Structured-Merge Tree)的原理,遇到這個問題的時候就不會認爲這是一個bug,也不會浪費了大把的時間來做無用功。所以建議大家不要抱怨每天的工作過於簡單枯燥,在開發過程中多去挖掘一些深層次的東西,不但讓你的技術的深度不斷加深,也會對你的編碼效率有很大的提升。

五、要有技術保障體系

性能優化不能一勞永逸,我個人覺得更是一場持久戰。不僅需要你能夠在某個特定時期做專項優化的攻堅戰,還要爲打好持久戰做出好的後勤補給,爲了能使App長期保持好的性能,不僅僅需要開發人員有良好的開發技能,還需要有一些技術保障和體系。下面簡單羅列我能想到的幾個方面。

  • 要有好的測試保障,這裏的測試保障不僅僅指的是測試人員的手動測試,更需要的自動化測試。要建立針對不同性能指標的專項自動化測試,建立一套從定時運行測試到測試結果的輸出等一套完整的自動化測試體系,能夠爲性能的保證提供堅實的數據支撐。

  • 引入關鍵性能指標上線准入制度。在開發階段,爲了不將有性能問題的代碼帶到線上,可以將比如啓動時間、FPS、安裝包大小等指標作爲關鍵指標,上線前進行自動化測試,如指標不達標,不允許上線。

  • 對於線上App使用APM進行監控,發現線上的性能問題,有及時的預警機制,能夠隨時解決線上的性能問題。

  • 開發過程中,代碼中需要有對性能保障的設計。 比如可以設計可複用的高性能控件,這樣其他開發人員在開發類似功能時,可以簡單複用,不僅提升了性能,而且大大節省了開發的時間。還有比如爲了防止App隨着版本迭代導致啓動時加載的服務越來約多,導致啓動變慢,可以設計App啓動器,把這些任務統一放到主界面加載完成後再執行,並且在組內開發人員中形成硬性的規範,但凡啓動時期不必須的服務,要麼不要執行,要麼統一放到啓動器的主界面加載完成的回調中執行。

  • 定期做一些性能優化方面的技術分享,不僅僅可以提高組內同學的開發技能,還可以活躍組內的技術氣氛。

以上我對iOS性能優化的總體原則做的總結,希望能夠對大家有一點點啓示。其中可能很多想法並不成熟,也希望大家能夠多多批評互相探討,共同進步。

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