Android app native代码性能分析
分析我們app中native層的C/C++代碼性能,能夠方便我們找出其中的性能瓶頸,并在稍后做有針對(duì)性的優(yōu)化。
下載android-ndk-profiler
工欲善其事,必先利其器,我們先要有良好的工具來支持我們做性能分析的愿望。android-ndk-profiler就是目前我們可用的比較好的工具。原來這個(gè)項(xiàng)目是托管在google的代碼托管服務(wù)器的,地址,但現(xiàn)在它已經(jīng)被遷移到gihub。訪問原來的地址時(shí),會(huì)自動(dòng)地被重定向到github上,地址。這樣也好,倒省掉我們這些天朝子民翻墻的麻煩了。
我們可以到github去下載android ndk profiler。可以下載master branch的zip壓縮包,也可以把整個(gè)項(xiàng)目直接git clone下來,git clone下來可能要更好一點(diǎn)。這個(gè)項(xiàng)目的目錄結(jié)構(gòu)大體如下(2015-06-25這天的版本):
hanpfei@hanpfei-ThundeRobot:~/android-ndk-profiler_repo$ ls -al 總用量 84 drwxrwxr-x 7 hanpfei hanpfei 4096 6月 25 11:26 . drwxr-xr-x 54 hanpfei hanpfei 4096 6月 25 11:27 .. -rw-rw-r-- 1 hanpfei hanpfei 35147 6月 24 19:29 COPYING drwxrwxr-x 2 hanpfei hanpfei 4096 6月 24 19:29 docs drwxrwxr-x 3 hanpfei hanpfei 4096 6月 25 11:26 example drwxrwxr-x 8 hanpfei hanpfei 4096 6月 25 11:26 .git -rw-rw-r-- 1 hanpfei hanpfei 365 6月 24 19:29 .gitignore drwxrwxr-x 2 hanpfei hanpfei 4096 6月 25 11:26 jni -rw-rw-r-- 1 hanpfei hanpfei 974 6月 24 19:29 Makefile -rw-rw-r-- 1 hanpfei hanpfei 122 6月 25 11:26 ndk-excludes.txt -rw-rw-r-- 1 hanpfei hanpfei 791 6月 24 19:29 README.mkd drwxrwxr-x 2 hanpfei hanpfei 4096 6月 24 19:29 test -rw-rw-r-- 1 hanpfei hanpfei 643 6月 25 11:26 .travis.yml這個(gè)項(xiàng)目中提供的例子、文檔什么的,可以參考一下。但真正需要被集成到我們項(xiàng)目里的就只有jni目錄下面的那些。
我們把jni目錄拷貝到另外一個(gè)地方,并重命名為android-ndk-profiler,比如:
hanpfei@hanpfei-ThundeRobot:~/android-ndk-profiler_repo$ cp -r jni ../android-ndk-profiler后面我們會(huì)再來說明為什么要這么做。
修改項(xiàng)目jni目錄下的Android.mk文件,加載android-ndk-profiler
將android-ndk-profiler集成進(jìn)我們項(xiàng)目的第一步,就是修改jni目錄下的Android.mk文加載android-ndk-profiler了:
# compile with profiling LOCAL_CFLAGS := -pgLOCAL_STATIC_LIBRARIES := android-ndk-profiler# at the end of Android.mk $(call import-module,android-ndk-profiler)如果項(xiàng)目的編譯還需要其它的flag,則把應(yīng)該把"-pg"加在LOCAL_CFLAGS行的最后面,或者在適當(dāng)?shù)奈恢眉右恍?#xff0c;使用+=語法來添加這個(gè)flag,比如:
LOCAL_CFLAGS += -pg -DP2P_PROFILING-pg是gcc的調(diào)試選項(xiàng),它們會(huì)將profiling信息加入到最終生成的二進(jìn)制代碼中,profiling信息包含了更多的調(diào)試信息。
LOCAL_STATIC_LIBRARIES的值需要與android-ndk-profiler的Android.mk文件中定義的LOCAL_MODULE值對(duì)應(yīng),$(call import-module,android-ndk-profiler)這一行中,call import-module為Android編譯系統(tǒng)的內(nèi)置命令,而android-ndk-profiler則要與項(xiàng)目的目錄名對(duì)應(yīng),這也就是上面我們?yōu)槭裁匆裫ni目錄copy,重命名為android-ndk-profiler的原因。
設(shè)置NDK_MODULE_PATH環(huán)境變量
到目前為止,我們的項(xiàng)目都還無法編譯通過。我們還需要設(shè)置NDK_MODULE_PATH環(huán)境變量。我們可以用export命令來設(shè)置這個(gè)環(huán)境變量,也可以將這個(gè)設(shè)置放在ndk-build命令中完成,而這個(gè)環(huán)境變量的值是android-ndk-profiler的父目錄。比如,我們剛剛將android-ndk-profiler的jni目錄拷貝到了用戶根目錄下的android-ndk-profiler目錄,那么這個(gè)環(huán)境變量就應(yīng)該被設(shè)置為~,即我們的用戶根目錄。
對(duì)于Eclipse環(huán)境,可以這樣來設(shè)置:在Package Explorer中,鼠標(biāo)選中項(xiàng)目,右鍵單擊彈出菜單,Propertities -> C/C++ Build -> Build command,在最后加上NDK_MODULE_PATH=~。比如像下面這樣:
ndk-build NDK_DEBUG=1 NDK_MODULE_PATH=~如果沒有做這樣的設(shè)置的話,編譯時(shí)會(huì)報(bào)錯(cuò),由Eclipse的Console我們可以看到這樣的報(bào)錯(cuò)信息:
/media/data/dev_tools/android-ndk-r9d/ndk-build NDK_DEBUG=1 Android NDK: jni/Android.mk: Cannot find module with tag 'android-ndk-profiler' in import path jni/Android.mk:101: *** Android NDK: Aborting. . Stop. Android NDK: Are you sure your NDK_MODULE_PATH variable is properly defined ? Android NDK: The following directories were searched: Android NDK:ucontext_t類型的定義
很不幸,在我們正確地設(shè)置了NDK_MODULE_PATH環(huán)境變量之后,還是無法通過編譯。Eclipse Console中的報(bào)錯(cuò)信息如下:
**** Build of configuration Default for project peerTester_udt **** /media/data/dev_tools/android-ndk-r9d/ndk-build NDK_DEBUG=1 NDK_MODULE_PATH=~ [armeabi-v7a] Gdbserver : [arm-linux-androideabi-4.6] libs/armeabi-v7a/gdbserver [armeabi-v7a] Gdbsetup : libs/armeabi-v7a/gdb.setup [armeabi-v7a] Compile thumb : android-ndk-profiler <= prof.c /home/hanpfei/android-ndk-profiler/prof.c: In function 'histogram_bin_incr': /home/hanpfei/android-ndk-profiler/prof.c:150:2: error: unknown type name 'ucontext_t' /home/hanpfei/android-ndk-profiler/prof.c:150:26: error: 'ucontext_t' undeclared (first use in this function) /home/hanpfei/android-ndk-profiler/prof.c:150:26: note: each undeclared identifier is reported only once for each function it appears in /home/hanpfei/android-ndk-profiler/prof.c:150:38: error: expected expression before ')' token /home/hanpfei/android-ndk-profiler/prof.c:151:41: error: request for member 'uc_mcontext' in something not a structure or union make: *** [obj/local/armeabi-v7a/objs-debug/android-ndk-profiler/prof.o] Error 1提示找不到ucontext_t類型的定義。這究竟又是怎么一回事呢?在github上,這個(gè)項(xiàng)目的all commits列表中,我們看到有這么幾筆commits的comments里提到了ucontext_t:
b22514a66c0477dd34eadf7039e404bfc6f38c1b
和
516eee06cb18429dedabd145c9f00b0b14ff65c8,
其中前者把jni/ucontext.h頭文件中ucontext_t的定義給刪了,而后者則干脆直接把這個(gè)頭文件給徹底移除了。
由b22514a66c0477dd34eadf7039e404bfc6f38c1b這筆commit的comments,我們大體可以了解到ucontext_t的定義被從項(xiàng)目中移除的原因。ucontext本是GNU C庫提供的一套標(biāo)準(zhǔn)的機(jī)制,用來創(chuàng)建、保存、切換用戶態(tài)執(zhí)行“上下文”,但無奈早期的NDK不支持這套機(jī)制,所以android-ndk-profiler項(xiàng)目就自己加了相關(guān)結(jié)構(gòu)的定義。但自r10d版本開始,官方NDK已經(jīng)是自帶了對(duì)這套機(jī)制的支持,所以原來項(xiàng)目中ucontext_t的定義就顯得多余了。
真操蛋,可憐了我們這群還在使用r9版NDK的人兒。如果能方便地把使用的NDK更新到r10d及之后的版本自然更好。如果不能,只有另想他法。
官方把jni/ucontext.h這個(gè)文件給刪了,那大不了我們把項(xiàng)目的repo reset幾筆change,重新找回這個(gè)文件就是了。reset到b22514a66c0477dd34eadf7039e404bfc6f38c1b之前的那個(gè)狀態(tài),大概就是reset 9筆changes:
hanpfei@hanpfei-ThundeRobot:~/android-ndk-profiler_repo$ git reset --hard HEAD~9OK,找回了我們要的ucontext.h文件了。此外,還需要修改prof.c文件來包含那個(gè)頭文件。
至此,我們的項(xiàng)目終于能夠順利通過編譯了。
添加對(duì)監(jiān)視函數(shù)的調(diào)用
經(jīng)過了上面的步驟,android-ndk-profiler的功能終于被順地編譯進(jìn)了我們的so。但要如何使android-ndk-profiler提供的功能運(yùn)轉(zhuǎn)起來呢?我們還需要在代碼的適當(dāng)位置,加入對(duì)監(jiān)視函數(shù)的調(diào)用。
默認(rèn)產(chǎn)生的profiling結(jié)果文件的路徑為/sdcard/gmon.out,這個(gè)路徑在prof.c中定義。因而我們需要讓我們的app具有在sdcard上寫文件的權(quán)限。我們可以在AndroidManifest.xml文件中加入下面一行:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />在我們native code的適當(dāng)位置,加入對(duì)監(jiān)視函數(shù)的調(diào)用:
/* in the start-up code */ monstartup("your_lib.so");/* in the onPause or shutdown code */ moncleanup();比如在start性質(zhì)的函數(shù)中加入對(duì)函數(shù)monstartup()的調(diào)用,在end性質(zhì)的函數(shù)中加入對(duì)moncleanup()函數(shù)的調(diào)用。要調(diào)用這兩個(gè)函數(shù),其它的一些基本設(shè)置必不可少:在調(diào)用這些函數(shù)的code文件中,inlcude相應(yīng)的頭文件,也就是prof.h;在Android.mk文件的搜索頭文件路徑列表中加入android-ndk-profiler的路徑,也就是變量LOCAL_C_INCLUDES加入~/android-ndk-profiler路徑。
產(chǎn)生并查看結(jié)果
moncleanup()執(zhí)行結(jié)束之后,就在sdcard上產(chǎn)生了結(jié)果文件。我們可以使用gprof命令來產(chǎn)生profiling的報(bào)表文件。我們需要先用adb pull命令將gmon.out文件拷貝到自己的PC上,然后執(zhí)行如下的命令產(chǎn)生結(jié)果:
$/media/data/dev_tools/android-ndk-r9d/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86/bin/arm-linux-androideabi-gprof libmoretvp2p.so > gprof.txt這個(gè)地方的so文件,是從項(xiàng)目的obj/local/armeabi-v7a/下copy出來的,而不是項(xiàng)目的libs/armeabi-v7a/下。使用后者來產(chǎn)生報(bào)表時(shí)會(huì)報(bào)錯(cuò):
hanpfei@hanpfei-ThundeRobot:~/p2pclient_prof$ /media/data/dev_tools/android-ndk-r9d/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86/bin/arm-linux-androideabi-gprof libmoretvp2p.so > gprof.txt /media/data/dev_tools/android-ndk-r9d/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86/bin/arm-linux-androideabi-gprof: file `libmoretvp2p.so' has no symbols提示no symbols。
我們可以用普通的文本編輯器打開我們?cè)谏弦徊街挟a(chǎn)生的gprof.txt文件:
Flat profile:Each sample counts as 0.01 seconds.% cumulative self self total time seconds seconds calls ms/call ms/call name 21.69 0.82 0.82 299 2.74 2.74 CRcvLossList::CRcvLossList(int)20.11 1.58 0.76 profCount12.70 2.06 0.48 299 1.61 1.61 CSndLossList::CSndLossList(int)5.29 2.26 0.20 systemMessage4.76 2.44 0.18 301 0.60 0.60 CRcvBuffer::~CRcvBuffer()3.44 2.57 0.13 299 0.43 0.43 CRcvBuffer::CRcvBuffer(CUnitQueue*, int)2.12 2.65 0.08 free_maps1.32 2.70 0.05 3052 0.02 0.02 CChannel::sendto(sockaddr const*, CPacket&) const1.32 2.75 0.05 std::istream::sentry::sentry(std::istream&, bool)1.06 2.79 0.04 31 1.29 3.29 MORETV::HttpDownloadTask::downloadByHttp(std::string const&, Poco::AutoPtr<MORETV::TsDownloadSession>&)0.79 2.82 0.03 13956 0.00 0.00 Poco::AutoPtr<MORETV::TransportStreamImpl>::operator->()0.79 2.85 0.03 7322 0.00 0.00 MORETV::TransportStreamImpl::write(int, char const*, int)0.79 2.88 0.03 4391 0.01 0.01 std::_Rb_tree<int, std::pair<int const, CUDTSocket*>, std::_Select1st<std::pair<int const, CUDTSocket*> >, std::less<int>, std::allocator<std::pair<int const, CUDTSocket*> > >::_M_lower_bound(std::_Rb_tree_node<std::pair<int const, CUDTSocket*> >*, std::_Rb_tree_node<std::pair<int const, CUDTSocket*> >*, int const&)0.79 2.91 0.03 sigemptyset0.53 2.93 0.02 33591 0.00 0.00 CChannel::recvfrom(sockaddr*, CPacket&) const0.53 2.95 0.02 1323 0.02 0.02 CACKWindow::store(int, int)0.53 2.97 0.02 CRcvQueue::worker(void*)0.53 2.99 0.02 std::string::replace(unsigned int, unsigned int, char const*, unsigned int)0.53 3.01 0.02 std::locale::locale() ...由這個(gè)結(jié)果,可以看到android-ndk-profiler工具本身的profCount()函數(shù)耗費(fèi)了我們好多的CPU時(shí)間唉。
各個(gè)字段的具體含義如下:
% time
This is the percentage of the total execution time your program spent in this function. These should all add up to 100%.
cumulative seconds
This is the cumulative total number of seconds the computer spent executing this functions, plus the time spent in all the functions above this one in this table.
self seconds
This is the number of seconds accounted for by this function alone. The flat profile listing is sorted first by this number.
calls
This is the total number of times the function was called. If the function was never called, or the number of times it was called cannot be determined (probably because the function was not compiled with profiling enabled), the calls field is blank.
self ms/call
This represents the average number of milliseconds spent in this function per call, if this function is profiled. Otherwise, this field is blank for this function.
total ms/call
This represents the average number of milliseconds spent in this function and its descendants per call, if this function is profiled. Otherwise, this field is blank for this function. This is the only field in the flat profile that uses call graph analysis.
name
This is the name of the function. The flat profile is sorted by this field alphabetically after the self seconds and calls fields are sorted.
Done。
總結(jié)
以上是生活随笔為你收集整理的Android app native代码性能分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android平台Chromium ne
- 下一篇: 读《Android 安全架构深究》