欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android系統(tǒng)優(yōu)化Ninja加快編譯

 更新時(shí)間:2022年08月09日 14:07:05   作者:飛起來(lái)_飛過(guò)來(lái)  
這篇文章主要為大家介紹了Android系統(tǒng)優(yōu)化使用Ninja加快編譯示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

背景

Android系統(tǒng)模塊代碼的編譯實(shí)在是太耗時(shí)了,即使寥寥幾行代碼的修改,也能讓一臺(tái)具有足夠性能的編譯服務(wù)器工作十幾分鐘以上(模塊單編),只為編出一些幾兆大小的jar和dex。

這里探究的是系統(tǒng)完成過(guò)一次整編后進(jìn)行的模塊單編,即m、mm、mmm等命令。

除此之外,一些不會(huì)更新源碼、編譯配置等文件的內(nèi)容的操作,如touch、git操作等,會(huì)被Android系統(tǒng)編譯工具識(shí)別為有差異,從而在編譯時(shí)重新生成編譯配置,重新編譯并沒(méi)有更新的源碼、重新生成沒(méi)有差異的中間文件等一系列嚴(yán)重耗時(shí)操作。

本文介紹關(guān)于編譯過(guò)程中的幾個(gè)階段,以及這些階段的耗時(shí)點(diǎn)/耗時(shí)原因,并最后給出一個(gè)覆蓋一定應(yīng)用場(chǎng)景的基于ninja的加快編譯的方法(實(shí)際上是裁剪掉冗余的編譯工作)。

環(huán)境

編譯服務(wù)器硬件及Android信息:

  • Ubuntu 18.04.4 LTS
  • Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz (28核56超線程)
  • MemTotal: 65856428 kB (62.8GiB)
  • AOSP Android 10.0
  • 僅修改某個(gè)Java文件內(nèi)部的boolean初始化值(true改false)
  • 不修改其他任何內(nèi)容,包括源碼、mk、bp的情況下,使用m單編模塊(在清理后,使用對(duì)比的ninja進(jìn)行單編)
  • 使用time計(jì)時(shí)
  • 此前整個(gè)系統(tǒng)已經(jīng)整編過(guò)一次
  • 編譯時(shí)不修改任何編譯配置文件如Android.mk

之所以做一個(gè)代碼修改量微乎其微的case,是因?yàn)橐治鼍幾g性能瓶頸,代碼變更量越小的情況下,瓶頸就越明顯,越有利于分析。

關(guān)鍵編譯階段和耗時(shí)分析

由于Makefile結(jié)構(gòu)復(fù)雜、不易調(diào)試、難以擴(kuò)展,因此Android決定將它替換掉。Android在7.0時(shí)引入了Soong,它將Android從Makefile的編譯架構(gòu)帶入到了ninja的時(shí)代。

Soong包含兩大模塊,其中Kati負(fù)責(zé)解析Makefile并轉(zhuǎn)換為.ninja,第二個(gè)模塊Ninja則基于生成的.ninja完成編譯。

Kati是對(duì)GNU Make的clone,并將編譯后端實(shí)現(xiàn)切換到ninja。Kati本身不進(jìn)行編譯,僅生成.ninja文件提供給Ninja進(jìn)行編譯。

Makefile/Android.mk -> Kati -> Ninja
Android.bp -> Blueprint -> Soong -> Ninja

因此在執(zhí)行編譯之前(即Ninja真正開(kāi)動(dòng)時(shí)),還有一些生成.ninja的步驟。關(guān)鍵編譯階段如下:

Soong的自舉(Bootstrap),將Soong本身編譯出來(lái)

系統(tǒng)代碼首次編譯會(huì)比較耗時(shí),其中一個(gè)原因是Soong要全新編譯它自己

遍歷源碼樹(shù),收集所有編譯配置文件(Makefile/Android.mk/Android.bp)

  • 遍歷、驗(yàn)證非常耗時(shí),多么強(qiáng)勁配置的機(jī)器都將受限于單線程效率和磁盤(pán)IO效率
  • 由于Android系統(tǒng)各模塊之間的依賴、引入,因此即使是單編模塊,Soong(Kati)也不得不確認(rèn)目標(biāo)模塊以外的路徑是否需要重新跟隨編譯。

驗(yàn)證編譯配置文件的合法性、有效性、時(shí)效性、是否應(yīng)該加入編譯,生成.ninja

  • 如果沒(méi)有任何更改,.ninja不需要重新生成
  • 最終生成的.ninja文件很大(In my case,1GB以上),有很明顯的IO性能效率問(wèn)題,顯然在查詢效率方面也很低下

最后一步,真正執(zhí)行編譯,調(diào)用ninja進(jìn)入多線程編譯

  • 由于Android加入了大量的代碼編譯期工作,如API權(quán)限控制檢查、API列表生成等工作(比如,生成系統(tǒng)API保護(hù)名單、插樁工作等等),因此編譯過(guò)程實(shí)際上不是完全投入到編譯中
  • 編譯過(guò)程穿插“泛打包工作”,如生成odex、art、res資源打包。雖然不同的“泛打包”可以多線程并行進(jìn)行,但是每個(gè)打包本身只能單線程進(jìn)行

下面將基于模塊單編(因開(kāi)發(fā)環(huán)境系統(tǒng)全新編譯場(chǎng)景頻率較低,不予考慮),對(duì)這四個(gè)關(guān)鍵階段進(jìn)行性能分析。

階段一:Soong bootstrap

在系統(tǒng)已經(jīng)整編過(guò)一次的情況下,Soong已經(jīng)完成了編譯,因此其預(yù)熱過(guò)程占整個(gè)編譯時(shí)間的比例會(huì)比較小。

在“環(huán)境”下,修改一行Framework代碼觸發(fā)差異進(jìn)行編譯。并且使用下面的命令進(jìn)行編譯。

time m services framework -j57

編譯實(shí)際耗時(shí)22m37s:

build completed successfully (22:37 (mm:ss)) ####
real    22m37.504s
user    110m25.656s
sys     12m28.056s

對(duì)應(yīng)的分階段耗時(shí)如下圖。

  • 可以看到,包括Soong bootstrap流程在內(nèi)的預(yù)熱耗時(shí)占比非常低,耗時(shí)約為11.6s,總耗時(shí)約為1357s,預(yù)熱耗時(shí)占比為0.8%。

  • Kati和ninja,也就是上述編譯關(guān)鍵流程的第2步和第3步,分別占了接近60%(820秒,13分鐘半)和約35%(521秒,8分鐘半)的耗時(shí),合計(jì)占比接近95%的耗時(shí)。

注:這個(gè)耗時(shí)是僅小幅度修改Java代碼后測(cè)試的耗時(shí)。如果修改編譯配置文件如Android.mk,會(huì)有更大的耗時(shí)。

小結(jié):看來(lái)在完成一次整編后的模塊單編,包括Soong bootstrap、執(zhí)行編譯準(zhǔn)備腳本、vendorsetup腳本的耗時(shí)占比很低,可以完全排除存在性能瓶頸的可能。

階段二:Kati遍歷、mk搜集與ninja生成

從上圖可以看到,Kati耗時(shí)占比很大,它的任務(wù)是遍歷源碼樹(shù),收集所有的編譯配置文件,經(jīng)過(guò)驗(yàn)證和篩選后,將它們解析并轉(zhuǎn)化為.ninja。

從性能角度來(lái)看,它的主要特點(diǎn)如下:

  • 它要遍歷源碼樹(shù),收集所有mk文件(In my case,有983個(gè)mk文件)
  • 解析mk文件(In my case,framework/base/Android.mk耗費(fèi)了~6800ms)
  • 生成并寫(xiě)入對(duì)應(yīng)的.ninja
  • 單線程

直觀展示如下,它是一個(gè)單線程的、IO速度敏感、CPU不敏感的過(guò)程:

Kati串行地處理文件,此時(shí)對(duì)CPU利用率很低,對(duì)IO的壓力也不高。

小結(jié):可以確定它的性能瓶頸來(lái)源于IO速度,單純?yōu)榫幾g實(shí)例分配更多的CPU資源也無(wú)益于提升Kati的速度。

階段三:Ninja編譯

SoongClone了一份GNU Make,并將其改造為Kati。即使我們沒(méi)有修改任何mk文件,前面Kati仍然會(huì)花費(fèi)數(shù)分鐘到數(shù)十分鐘的工作耗時(shí),只為了生成一份能夠被Ninja.ninja的生成工具能夠識(shí)別的文件。接下來(lái)是調(diào)用Ninja真正開(kāi)始編譯工作。

從性能角度來(lái)看,它的主要特點(diǎn)如下:

  • 根據(jù)目標(biāo)target及依賴,讀取前面生成的.ninja配置,進(jìn)行編譯
  • 比較獨(dú)立,不與前面的組件,如blueprint、kati等耦合,只要.ninja文件中能找到target和build rule就能完成編譯
  • 多線程

直觀展示如下,Ninja將會(huì)根據(jù)傳入的并行任務(wù)數(shù)參數(shù)啟動(dòng)對(duì)應(yīng)數(shù)量的線程進(jìn)行編譯。Ninja編譯階段會(huì)真正的啟動(dòng)多線程。但做不到一直多線程編譯,因?yàn)椴糠蛛A段如部分編譯目標(biāo)(比如生成一個(gè)API文檔)、泛打包階段等本身無(wú)法多線程并行執(zhí)行。

可以看到此時(shí)CPU利用率應(yīng)該是可以明顯上升的。但是耗時(shí)較大的階段僅啟用了幾個(gè)線程,后面的階段和最后的圖形很細(xì)(時(shí)間占比很小)的階段才用起來(lái)更多的線程。

其中,一些階段(圖中時(shí)間占比較長(zhǎng)的幾條記錄)沒(méi)能跑滿資源的原因是這些編譯目標(biāo)本身不支持并行,且本次編譯命令指定的目標(biāo)已經(jīng)全部“安排”了,不需要調(diào)動(dòng)更多資源啟動(dòng)其他編譯目標(biāo)的工作。當(dāng)編譯整個(gè)系統(tǒng)時(shí)就能夠跑滿了。

最后一個(gè)階段(圖中最后的幾列很細(xì)的記錄)雖然跑滿了所有線程資源,但是運(yùn)行時(shí)間很短。這是因?yàn)楸綾ase進(jìn)行編譯分析的過(guò)程中,僅修改了一行代碼來(lái)觸發(fā)編譯。因編譯工作量很小,所以這幾列很細(xì)。

小結(jié):我們看到,Ninja編譯啟動(dòng)比較快,這表明Ninja對(duì).ninja文件的讀取解析并不敏感。整個(gè)過(guò)程也沒(méi)有看到顯著的耗時(shí)點(diǎn)。且最后面編譯量很小,表明Ninja能夠確保增量編譯、未更新不編譯。

編譯優(yōu)化

本節(jié)完成點(diǎn)題——Android系統(tǒng)編譯優(yōu)化:使用Ninja加快編譯。

根據(jù)前面分析的小結(jié),可以總結(jié)性能瓶頸:

  • Kati遍歷、生成太慢,受限于IO速率
  • Kati吞吐量太低,單線程
  • 不論有無(wú)更新均重新解析Makefile

利用Ninja進(jìn)行編譯優(yōu)化的思路是,大多數(shù)場(chǎng)景,可以舍棄Kati的工作,僅執(zhí)行Ninja的工作,以節(jié)省掉60%以上的時(shí)間。其核心思路,也是制約條件,即在不影響編譯正確性的前提下,舍棄不必要的Kati編譯工作。

  • 使用Ninja直接基于.ninja文件進(jìn)行編譯來(lái)改善耗時(shí):

結(jié)合前面的分析,容易想到,如果目標(biāo)被構(gòu)建前,能夠確保mk文件沒(méi)有更新也不需要重新生成一長(zhǎng)串的最終編譯目標(biāo)(即.ninja),那么make命令帶來(lái)的Soong bootstrap、Kati等工作完全是重復(fù)的冗余的——這個(gè)性質(zhì)Soong和Kati自己識(shí)別不出來(lái),它們會(huì)重復(fù)工作一次。

既重新生成.ninja是冗余的,那么直接命令編譯系統(tǒng)根據(jù)指定的.ninja進(jìn)行編譯顯然會(huì)節(jié)省大量的工作耗時(shí)。ninja命令is the key:

使用源碼中自帶的ninja:

./prebuilts/build-tools/linux-x86/bin/ninja --version
1.8.2.git

對(duì)比最上面列出的make命令的編譯,這里用ninja編譯同樣的目標(biāo):

 time ./prebuilts/build-tools/linux-x86/bin/ninja 
 -j 57 -v -f out/combined-full_xxxxxx.ninja services framework

ninja自己識(shí)別出來(lái)CPU平臺(tái)后,默認(rèn)使用-j58。這里為了對(duì)比上面的m命令,使用-j57編譯

-f參數(shù)指定.ninja文件。它是編譯配置文件,在Android中由Kati生成。這里文件名用'x'替換修改

編譯結(jié)果,對(duì)比上面的m,有三倍的提升:

real    7m57.835s
user    97m12.564s
sys     8m31.756s

編譯耗時(shí)為8分半,僅make的三分之一。As we can see,當(dāng)能夠確保編譯配置沒(méi)有更新,變更僅存在于源碼范圍時(shí),使用Ninja直接編譯,跳過(guò)Kati可以取得很顯著的提升

直接使用ninja:

./prebuilts/build-tools/linux-x86/bin/ninja 
-j $MAKE_JOBS -v -f out/combined-*.ninja <targets...>

對(duì)比匯總

這里找了一個(gè)其他項(xiàng)目的編譯Demo,該Demo的特點(diǎn)是本身代碼較簡(jiǎn)單,編譯配置也較簡(jiǎn)單,整體編譯工作較少,通過(guò)make編譯的大部分耗時(shí)來(lái)自soong、make等工具自身的消耗,而真正執(zhí)行編譯的ninja耗時(shí)占比極其低。由于ninja本身跳過(guò)了soong,因此可以跳過(guò)這一無(wú)用的繁瑣的耗時(shí)??梢钥吹较旅妫琻inja編譯iperf僅花費(fèi)10秒。這個(gè)時(shí)間如果給soong來(lái)編譯,預(yù)熱都不夠。

$ -> f_ninja_msf iperf
Run ninja with out/combined-full_xxxxxx.ninja to build iperf.
====== ====== ======
Ninja: ./prebuilts/build-tools/linux-x86/bin/ninja@1.8.2.git
Ninja: build with out/combined-full_xxxxxx.ninja
Ninja: build targets iperf
Ninja: j72
====== ====== ======
time /usr/bin/time ./prebuilts/build-tools/linux-x86/bin/ninja -j 72 -f out/combined-full_xxxxxx.ninja iperf
[24/24] Install: out/target/product/xxxxxx/system/bin/iperf
53.62user 11.09system 0:10.17elapsed 636%CPU (0avgtext+0avgdata 5696772maxresident)
4793472inputs+5992outputs (4713major+897026minor)pagefaults 0swaps
real    0m10.174s
user    0m53.624s
sys     0m11.096s

下面給出soong編譯的恐怖耗時(shí):

$ -> rm out/target/product/xxxxxx/system/bin/iperf
$ -> time m iperf -j72
...
[100% 993/993] Install: out/target/product/xxxxxx/system/bin/iperf
#### build completed successfully (14:45 (mm:ss)) ####
real    14m45.164s
user    23m40.616s
sys     11m46.248s

As we can see,m和ninja一個(gè)是10+ minutes,一個(gè)是10+ seconds,比例是88.5倍。

以上就是Android系統(tǒng)優(yōu)化Ninja加快編譯的詳細(xì)內(nèi)容,更多關(guān)于Android Ninja加快編譯的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android使用Kotlin和RxJava 2.×實(shí)現(xiàn)短信驗(yàn)證碼倒計(jì)時(shí)效果

    Android使用Kotlin和RxJava 2.×實(shí)現(xiàn)短信驗(yàn)證碼倒計(jì)時(shí)效果

    本篇文章主要介紹了Android使用Kotlin和RxJava 2.×實(shí)現(xiàn)短信驗(yàn)證碼倒計(jì)時(shí)效果,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2017-12-12
  • android開(kāi)發(fā)教程之實(shí)現(xiàn)toast工具類

    android開(kāi)發(fā)教程之實(shí)現(xiàn)toast工具類

    這篇文章主要介紹了android開(kāi)發(fā)中需要的toast工具類,需要的朋友可以參考下
    2014-05-05
  • 詳解Android截屏事件監(jiān)聽(tīng)

    詳解Android截屏事件監(jiān)聽(tīng)

    本篇文章主要介紹了Android截屏事件監(jiān)聽(tīng),Android系統(tǒng)沒(méi)有直接對(duì)截屏事件監(jiān)聽(tīng)的接口,本文介紹了2種方法,有興趣的可以了解一下。
    2016-12-12
  • Android使用Activity實(shí)現(xiàn)簡(jiǎn)單的可輸入對(duì)話框

    Android使用Activity實(shí)現(xiàn)簡(jiǎn)單的可輸入對(duì)話框

    大家在做彈出對(duì)話框效果的時(shí)候最容易想到的是用Dialog顯示,但其實(shí)彈出對(duì)話框的實(shí)現(xiàn)效果有兩種:Dialog和Activity,那么下面這篇文章就來(lái)給大家介紹了關(guān)于Android使用Activity如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的可輸入對(duì)話框的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。
    2017-10-10
  • Android初學(xué)者必須知道的10個(gè)技術(shù)

    Android初學(xué)者必須知道的10個(gè)技術(shù)

    本篇內(nèi)容給大家整理10個(gè)作為Android初學(xué)者必須要了解和會(huì)用的技術(shù)以及詳細(xì)代碼分析,需要的朋友收藏下慢慢學(xué)習(xí)吧。
    2017-12-12
  • Gradle配置教程之自定義APK名稱與輸出路徑

    Gradle配置教程之自定義APK名稱與輸出路徑

    Gradle是一個(gè)基于JVM的富有突破性構(gòu)建工具,下面這篇文章主要給大家介紹了關(guān)于Gradle配置教程之自定義APK名稱與輸出路徑的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-03-03
  • Android動(dòng)態(tài)更換應(yīng)用圖標(biāo)詳情

    Android動(dòng)態(tài)更換應(yīng)用圖標(biāo)詳情

    這篇文章主要介紹了Android動(dòng)態(tài)更換應(yīng)用圖標(biāo)詳情,文章圍繞主題展開(kāi)詳細(xì)的介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-07-07
  • Android開(kāi)發(fā)之TableLayout表格布局

    Android開(kāi)發(fā)之TableLayout表格布局

    這篇文章主要為大家詳細(xì)介紹了Android開(kāi)發(fā)之TableLayout表格布局,表格布局模型是以行列的形式管理子控件,對(duì)TableLayout表格布局感興趣的小伙伴們可以參考一下
    2016-03-03
  • Android Studio利用AChartEngine制作餅圖的方法

    Android Studio利用AChartEngine制作餅圖的方法

    閑來(lái)無(wú)事,發(fā)現(xiàn)市面上好多app都有餅圖統(tǒng)計(jì)的功能,得空自己實(shí)現(xiàn)一下,下面這篇文章主要給大家介紹了關(guān)于Android Studio利用AChartEngine制作餅圖的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起看看吧
    2018-10-10
  • Android通話默認(rèn)打開(kāi)揚(yáng)聲器的方法

    Android通話默認(rèn)打開(kāi)揚(yáng)聲器的方法

    這篇文章主要介紹了Android通話默認(rèn)打開(kāi)揚(yáng)聲器的方法.小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08

最新評(píng)論