什么是優(yōu)化
首先我們先破題,來(lái)談?wù)劇皟?yōu)化”這個(gè)事情。通常情況下,我們說(shuō)到優(yōu)化的時(shí)候,往往會(huì)伴隨著對(duì)之前系統(tǒng)的吐槽?;蚴遣缓糜?,或是性能低,或是用起來(lái)很麻煩。巴拉巴拉。是的,當(dāng)我們對(duì)原先的系統(tǒng)有槽點(diǎn)的時(shí)候,我們會(huì)談到“優(yōu)化”。而“優(yōu)化”的前提也是,之前已經(jīng)有過(guò)一個(gè)東西存在,而且真對(duì)目前的場(chǎng)景應(yīng)景不再適合。這個(gè)是有需要對(duì)原有系統(tǒng)進(jìn)行調(diào)整,以滿(mǎn)足當(dāng)前的場(chǎng)景與需求。那么所謂優(yōu)化即是:對(duì)原有系統(tǒng)進(jìn)行有目的的改造。
好吧,這聽(tīng)起來(lái)雖然說(shuō)了什么,但其實(shí)什么都沒(méi)說(shuō)。因?yàn)檫@是一句大實(shí)話(huà)。
but,我們仔細(xì)分析一下,我們要進(jìn)行優(yōu)化必須能夠:
對(duì)原有系統(tǒng)的問(wèn)題有所了解
了解目前場(chǎng)景和需求
有目的性的改造原有系統(tǒng)
我們來(lái)說(shuō)一個(gè)我們通常會(huì)遇到的例子,也是在面試的時(shí)候會(huì)遇到的問(wèn)題–“UItableView的性能優(yōu)化”。其實(shí)每次有人問(wèn)我這個(gè)問(wèn)題,我內(nèi)心都有千萬(wàn)只“草泥馬”奔騰而過(guò)。沒(méi)有具體的問(wèn)題場(chǎng)景,只單單跑出來(lái)這樣一個(gè)問(wèn)題。是可以和他扯什么圖片內(nèi)存緩存了,避免圓角的使用了,預(yù)渲染,預(yù)加載了之類(lèi)的東西。但是這些東西,真的對(duì)于在解決他們TableView卡頓的問(wèn)題有效嗎,不見(jiàn)得。套用《安娜卡列尼娜》一句話(huà):
流暢的UItableView都是相似的,不流暢的UItableView各有各的不幸。
好了,吐槽到此為止。吐槽的目的是為了說(shuō)明一點(diǎn),你要進(jìn)行優(yōu)化,必須有一個(gè)特定的場(chǎng)景。在一個(gè)受限的范圍內(nèi)進(jìn)行優(yōu)化,因?yàn)檫@樣目的是可控的。漫無(wú)邊際的優(yōu)化,和別人基于方法論的建議之類(lèi)的東西,不一定對(duì)當(dāng)前的問(wèn)題有幫助。
比如,之前我們?cè)谧龅囊粋€(gè)社交類(lèi)的App中,首頁(yè)使用了UItableView,老板說(shuō)怎么用著這么卡頓。然后我們就開(kāi)始了“優(yōu)化”。
首先,我們知道我們要優(yōu)化的是第一個(gè)tab的tableview的滑動(dòng)效率的問(wèn)題。那總得有個(gè)監(jiān)控的指標(biāo)吧。對(duì)于程序猿來(lái)說(shuō),感覺(jué)這個(gè)不卡了,或者感覺(jué)這個(gè)卡,這個(gè)東西太模糊了。無(wú)法衡量啊。所以一定要量化。對(duì)于界面來(lái)講就是大家常說(shuō)的FPS,每秒幀率。于是我們測(cè)量了一下幀率,平均下來(lái)是25FPS。ou my god!的確是有點(diǎn)卡。
然后我們知道對(duì)于ios來(lái)說(shuō)如果能達(dá)到60FPS,那界面絕對(duì)不會(huì)有卡頓的感覺(jué)了。而很少有應(yīng)用能達(dá)到這個(gè)水準(zhǔn)。那么我們給自己設(shè)置了一個(gè)目標(biāo)45FPS。btw,這個(gè)目標(biāo)只是個(gè)階段性目標(biāo)。
好了下面的過(guò)程,就是朝著這個(gè)目標(biāo)前進(jìn)了。當(dāng)然我們知道,造成FPS較低的原因,一般都是主線程做了太多的事情,導(dǎo)致幀率降低。這只是個(gè)大方向。而對(duì)我們來(lái)講,我們需要精準(zhǔn)的知道,主線程都做了些什么事情,導(dǎo)致幀率降低。
首先,我們發(fā)現(xiàn)的是,讀取圖片IO的過(guò)程發(fā)生在了主線程。IO過(guò)程一般是比較耗時(shí)的,于是我們像把該過(guò)程移到了后臺(tái)線程中處理。發(fā)現(xiàn)幀率能夠提高到33FPS,這還不夠啊。革命尚未完成,同志仍需努力。
之后的過(guò)程中,我們把布局預(yù)處理,還有圓角,數(shù)據(jù)預(yù)加載之類(lèi)的事情做上去之后,終于基本達(dá)到45。階段性目標(biāo)完成。
好了這是一個(gè)優(yōu)化的例子:始于發(fā)現(xiàn)問(wèn)題,止于目標(biāo)達(dá)成。而重要的是其過(guò)程,描述問(wèn)題?。。?!
分析問(wèn)題 (定性or定量)
其實(shí),我一直比較堅(jiān)信一句話(huà):當(dāng)你能夠準(zhǔn)確的描述一個(gè)問(wèn)題的時(shí)候,你到解決問(wèn)題就沒(méi)剩幾步了。比如剛才說(shuō)的卡頓的問(wèn)題,我們當(dāng)時(shí)是這么描述的:圖片讀取發(fā)生了主線程,主線程中有一部分CPU片段用于文件讀取和圖片解碼,造成主線程阻塞,從而導(dǎo)致幀率下降。當(dāng)描述到這里的時(shí)候,解決方案就比較顯而易見(jiàn)了,挪唄。搞到其他線程中之行。把主線程空出來(lái)。
而上面的這個(gè)描述還只是一個(gè)定性的描述分析。只是闡述了現(xiàn)象。雖然能夠解決了一個(gè)問(wèn)題,但是對(duì)整體問(wèn)題的貢獻(xiàn)有多大,也未可知。所以我們可以當(dāng)時(shí)完全可以這樣描述:我們圖片緩存在文件系統(tǒng)的平均大小是1MB,其讀取時(shí)間為10.7ms,圖片格式為jpeg,解碼一個(gè)1M的圖片耗時(shí)是60ms,而我們知道60FPS,每幀給主線程用來(lái)處理任務(wù)的CPU時(shí)間為17.7S,也就說(shuō)這個(gè)地方占用了大量CPU時(shí)間片來(lái)處理圖片讀與解碼操作,從而造成了CPU阻塞,造成幀率沒(méi)有達(dá)到60ms。
當(dāng)我們使用定量的描述的時(shí)候,我們能夠比較精確的知道,一個(gè)小問(wèn)題,對(duì)于大問(wèn)題來(lái)說(shuō)到底意味著什么。而定量分析的方案中,當(dāng)然包含了很多更多的細(xì)節(jié)信息,尤其是數(shù)據(jù)信息。這些也正是定量分析的優(yōu)勢(shì)所在。BUT,定量分析是一個(gè)非常耗時(shí)耗力的事情,你要拿到這么多的數(shù)據(jù),你勢(shì)必要付出很多時(shí)間,在采集這些數(shù)據(jù)上面。對(duì)于app開(kāi)發(fā)來(lái)講,除非公司給了足夠的資源(尤其是時(shí)間),你才能像個(gè)研究者一樣去采集這些數(shù)據(jù),一般情況是,大概都會(huì)止步到定性分析這一步。其實(shí)這也是看具體問(wèn)題而定了。
不過(guò)無(wú)論你是使用定性分析的方式還是定量分析的方式。我們的目標(biāo)是為了找到能夠準(zhǔn)確表述問(wèn)題的方式,并且定位問(wèn)題,以求找到解決方案。而為了達(dá)到這個(gè)目的一般情況下我們可以使用兩種方式:
你的編程功底和對(duì)iOS的了解程度都很深,那么完全可以從一些原理性的事情上去分析。我們稱(chēng)之為:邏輯分析法。
或許你的編程功底很深,或許很淺,或許你嘗試分析而沒(méi)有結(jié)果。那么可以使用改改代碼試試的方法了。我們稱(chēng)之為:實(shí)驗(yàn)法。
邏輯分析法, 原理性分析
哈哈,套用馬哲的一句話(huà):事物是普遍聯(lián)系的。既然是普遍聯(lián)系的,不說(shuō)必然存在因果,那么通過(guò)一定的邏輯分析。是可以找到他們之間的一些蛛絲馬跡的關(guān)聯(lián)的。這些關(guān)聯(lián)或許可以解釋一些什么。比如剛才卡頓的問(wèn)題:原理就是主線程CPU被消耗過(guò)多,無(wú)法及時(shí)處理UI任務(wù)導(dǎo)致的。這只是一個(gè)例子。
我們進(jìn)行邏輯分析的目的,是為了找到我們的某些代碼和問(wèn)題之間的因果性聯(lián)系。就是說(shuō),我們能夠明確知道造成UI卡頓的問(wèn)題,就是因?yàn)镮O的問(wèn)題之類(lèi)。這個(gè)話(huà)題說(shuō)起來(lái),比較深邃了。其中絕大部分實(shí)踐的方法可以從《數(shù)理邏輯》這本書(shū)中找到。不過(guò)這是本講數(shù)學(xué)的書(shū),咱們得稍微換下腦子,把其中的定理,在編程中應(yīng)用一下。因?yàn)槲乙仓皇且鈺?huì)了其中的某些東西,講出來(lái)還沒(méi)有那么功底。就只能麻煩各位自己去琢磨了。:)
實(shí)驗(yàn)法,Assume-Action-Response-Test-Assume
我稱(chēng)這個(gè)過(guò)稱(chēng)為AARTA。這是一個(gè)一直往復(fù)的過(guò)程,在分析的過(guò)程中,你得一次次的重復(fù)這個(gè)過(guò)程來(lái)找到真正問(wèn)題的所在。其實(shí),這個(gè)方法比較常應(yīng)用在改BUG這個(gè)場(chǎng)景上。其實(shí)如果從廣義上講,按照上面咱們對(duì)技術(shù)優(yōu)化的定義,改bug也算是一種優(yōu)化。只不過(guò)這個(gè)場(chǎng)景比價(jià)特殊而已。當(dāng)無(wú)法準(zhǔn)確的分析原理,或者當(dāng)前程序的復(fù)雜性過(guò)高(低內(nèi)聚高耦合)已經(jīng)超出人腦的計(jì)算能力范圍的時(shí)候,那么就可以“猜”了。
(1)假設(shè) assume
根據(jù)以往的經(jīng)驗(yàn)來(lái),設(shè)定一個(gè)和問(wèn)題域相關(guān)的假設(shè)。比如UI卡頓的問(wèn)題,你懷疑是不是因?yàn)閳D片的問(wèn)題呢。那么現(xiàn)在就假設(shè)是圖片的問(wèn)題!
(2) 嘗試進(jìn)行修改 action
既然假設(shè)是圖片的問(wèn)題,那么就把UIImageView從Cell上刪掉吧。
(3)看程序的反饋 Response
重新運(yùn)行一遍程序,看一下程序運(yùn)行的效果。FPS是否有所改善,而且改善的幅度有多大。
(4)Test
根據(jù),程序的反饋和我們預(yù)先設(shè)定的目標(biāo)來(lái)判斷一下,當(dāng)前改動(dòng)是否滿(mǎn)足了我們?cè)O(shè)計(jì)的目標(biāo)。如果有,那么你大概就找到了問(wèn)題的一個(gè)原因。如果沒(méi)有那么進(jìn)行下一步。
(5) 重新提出假設(shè) Assume
既然不是圖片的問(wèn)題,那么會(huì)不會(huì)是其他事情上耗費(fèi)了CPU呢。比如布局樣式的計(jì)算。那么重新假設(shè)是局部樣式的問(wèn)題。在執(zhí)行(2)過(guò)程。
所謂實(shí)驗(yàn),即是大膽假設(shè),小心取證,如此往復(fù),以求終解。
監(jiān)控
上面只是進(jìn)行了一些方法論的探討。但是有一件事情,是若要優(yōu)化一定要做的。那就是“監(jiān)控”。
監(jiān)控是個(gè)非常重要的東西。
監(jiān)控是個(gè)非常重要的東西。
監(jiān)控是個(gè)非常重要的東西。
重要的事情說(shuō)三遍。尤其是對(duì)于運(yùn)行在生產(chǎn)環(huán)境的程序。這就像是一個(gè)體檢,你得實(shí)時(shí)掌控程序的運(yùn)行情況,知道問(wèn)題出在了哪里,甚至有些時(shí)候知道:哎呀,出問(wèn)題了。沒(méi)有監(jiān)控,程序一旦上線之后,就像脫韁的野馬,跑到哪里,做了什么,你就是一頭忙然了。突然有一天,老板說(shuō)有人反饋咱們的app經(jīng)常崩潰,當(dāng)你沒(méi)有crash監(jiān)控,這個(gè)你都不知道從哪里查起。
而且,監(jiān)控也是優(yōu)化的數(shù)據(jù)來(lái)源。他能夠通過(guò)數(shù)據(jù)的指標(biāo)來(lái)非常直觀的告訴你,程序哪里有問(wèn)題,你優(yōu)化之后,效果是怎樣的。現(xiàn)在網(wǎng)上有很多這方面的服務(wù)提供出來(lái),比如bugly之類(lèi)的,甚至有些是APM(application performance manager),直接監(jiān)控到程序的運(yùn)行狀態(tài)和性能。google一下,能搜出不少來(lái)??梢宰们椋瑧?yīng)用在自己開(kāi)發(fā)的app中。
總結(jié)
說(shuō)了半天,總結(jié)一下。優(yōu)化是在可控的范圍內(nèi)有目的性的對(duì)現(xiàn)有程序的修改。一般可以使用邏輯分析法和實(shí)驗(yàn)法來(lái)定位、分析、描述問(wèn)題?;蛘叨ㄐ曰蛘叨俊o(wú)論哪種,要想優(yōu)化,你得先建立起對(duì)自己app運(yùn)行的監(jiān)控體系。