原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明出處咙冗,多謝沾歪!
如果cpu頻率、調(diào)度 和 compiler filter都一一排除了雾消,沒(méi)問(wèn)題灾搏。那接下來(lái)就看是否有具體方法耗時(shí)。
一仪或、常用的分析手段:
1.systrace
這里可按systrace中各個(gè)階段來(lái)逐段對(duì)比分析确镊,當(dāng)然這里也分冷熱啟。
冷啟動(dòng)可以拆分如下若干階段:
deliver input / fork process / bind application / activity start / doFrame / drawFrame / SF invalidate&refresh
熱啟動(dòng)就主要考慮繪制和渲染了范删。
看是否差距集中在一個(gè)某個(gè)階段內(nèi)蕾域,如果是特定區(qū)域的差異那么就來(lái)針對(duì)具體方法耗時(shí)進(jìn)行分析。
找到有差異的階段可以通過(guò)加trace,來(lái)縮小范圍和細(xì)化具體方法旨巷。
各層加trace的方式:
APP:
Trace.beginSection("");
Trace.endSection();
注:抓systrace的時(shí)候需要指定對(duì)應(yīng)的app進(jìn)程巨缘。
系統(tǒng)java層:
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate”);
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
注:前面的tag參數(shù)對(duì)應(yīng)的抓systrace的開(kāi)關(guān)選項(xiàng)。
Native:
#define ATRACE_TAG ATRACE_TAG_ALWAYS
#include <utils/Trace.h> // for c++
#include <cutils/trace.h> // for c
ATRACE_CALL();
or
ATRACE_BEGIN("");
ATRACE_END();
2. TraceView or AS profile cpu
對(duì)于app的問(wèn)題采呐,可以借助traceView 或者 as profile cpu檢查 CPU Activity 和函數(shù)跟蹤來(lái)幫助定位耗時(shí)方法若锁,不斷縮小范圍來(lái)定位問(wèn)題。
3. 反編譯
對(duì)于三方app斧吐,沒(méi)有源碼又固,可以通過(guò)oatdump反編譯來(lái)分析:
adb shell oatdump --oat-file=/data/app/包名/oat/arm64/base.odex > demo.txt
字節(jié)碼命令說(shuō)明
二、實(shí)戰(zhàn)舉例:高德地圖耗時(shí)分析
1.發(fā)現(xiàn)問(wèn)題
在cpu頻率煤率、調(diào)度 和 compiler filter都一一排除了的前提下仰冠,通過(guò)systrace來(lái)分析具體啟動(dòng)各階段耗時(shí)情況。
Android N:
Android O:
發(fā)現(xiàn)MapContainer執(zhí)行時(shí)間比較長(zhǎng)蝶糯,MapContainer 是高德自己實(shí)現(xiàn)的類洋只,應(yīng)該是高德自己的實(shí)現(xiàn)方式在不同 Android 版本上差別比較大。因?yàn)楦叩率侨綉?yīng)用昼捍,沒(méi)有代碼识虚,所以借助traceView來(lái)分析。
注:如果是冷啟動(dòng)可以使用命令行來(lái)抓榷什纭:
1)啟動(dòng)指定 Activity 同時(shí)啟動(dòng) trace
am start -n com.stan.note.newdemo/.MainActivity --start-profiler /data/local/tmp/test.trace
am profile com.stan.note.newdemo stop
2)啟動(dòng)指定 Activity 同時(shí)啟動(dòng) trace, 自動(dòng)結(jié)束
am start -n com.stan.note.newdemo/.MainActivity -P /data/local/tmp/test.trace
通過(guò) TraceView 發(fā)現(xiàn)有兩個(gè)相關(guān)的方法非常耗時(shí):
com.autonavi.mao.core.OverlayManager.init ()V
com.autonavi.minimap.commute.CommuteOverlay.init ()V
下面通過(guò)oatdump來(lái)反編譯這兩個(gè)方法
截取一個(gè)小片段:
void com.autonavi.minimap.commute.CommuteOverlay.init() (dex_method_idx=20281)
DEX CODE:
0x0000: 1202 | const/4 v2, #+0
0x0001: e530 0800 | iget-object-quick v0, v3, // offset@8
0x0003: 7110 6006 0000 | invoke-static {v0}, android.view.LayoutInflater android.view.LayoutInflater.from(android.content.Context) // method@1632
0x0006: 0c00 | move-result-object v0
0x0007: 6001 a235 | sget v1, I com.autonavi.minimap.R$layout.commute_marker_layout // field@13730
0x0009: e930 1200 1002 | invoke-virtual-quick {v0, v1, v2}, // vtable@18
0x000c: 0c00 | move-result-object v0
這里就想找到art指令中對(duì)應(yīng)的函數(shù)并加上trace担锤,來(lái)確定是哪個(gè)具體函數(shù)耗時(shí)。
比如sget指令郊闯,對(duì)應(yīng)到如下解釋器解釋指令
art/runtime/interpreter/mterp/arm64/op_sget.S
%default { "is_object":"0", "helper":"MterpGet32Static", "extend":"" }
2 /*
3 * General SGET handler wrapper.
4 *
5 * for: sget, sget-object, sget-boolean, sget-byte, sget-char, sget-short
6 */
7 /* op vAA, field//BBBB */
8
9 .extern $helper
10 EXPORT_PC
11 FETCH w0, 1 // w0<- field ref BBBB
12 ldr x1, [xFP, #OFF_FP_METHOD]
13 mov x2, xSELF
14 bl $helper
15 ldr x3, [xSELF, #THREAD_EXCEPTION_OFFSET]
16 lsr w2, wINST, #8 // w2<- AA
17 $extend
18 PREFETCH_INST 2
19 cbnz x3, MterpException // bail out
20.if $is_object
21 SET_VREG_OBJECT w0, w2 // fp[AA]<- w0
22.else
23 SET_VREG w0, w2 // fp[AA]<- w0
24.endif
25 ADVANCE 2
26 GET_INST_OPCODE ip // extract opcode from rINST
27 GOTO_OPCODE ip
這部分是匯編指令妻献,具體指令執(zhí)行不耗時(shí)蛛株,肯定是函數(shù)耗時(shí)团赁。函數(shù)調(diào)用$helper 對(duì)應(yīng)MterpGet32Static函數(shù)。
在對(duì)應(yīng)的函數(shù)處加trace
art/runtime/interpreter/mterp/mterp.cc
#include "base/systrace.h"
extern "C" int MterpSet32Static(uint32_t field_idx,
int32_t new_value,
ArtMethod* referrer,
Thread* self)
REQUIRES_SHARED(Locks::mutator_lock_) {
return MterpSetStatic<int32_t, Primitive::kPrimInt>(field_idx,
new_value,
referrer,
self,
&ArtField::SetInt<false>);
}
在對(duì)應(yīng)MterpSetStatic方法加trace.
template <typename return_type, Primitive::Type primitive_type>
ALWAYS_INLINE return_type MterpGetStatic(uint32_t field_idx,
ArtMethod* referrer,
Thread* self,
return_type (ArtField::*func)(ObjPtr<mirror::Object>))
REQUIRES_SHARED(Locks::mutator_lock_) {
ScopedTrace trace(__FUNCTION__);
return_type res = 0; // On exception, the result will be ignored.
ArtField* f =
FindFieldFromCode<StaticPrimitiveRead, false>(field_idx,
referrer,
self,
primitive_type);
if (LIKELY(f != nullptr)) {
ObjPtr<mirror::Object> obj = f->GetDeclaringClass();
res = (f->*func)(obj);
}
return res;
}
2.分析問(wèn)題
MterpGetStatic 就是去獲取一個(gè)類的靜態(tài)成員, 為什么會(huì)用掉 85 ms ?apk中dex文件會(huì)在art中轉(zhuǎn)化為一個(gè)DexFile對(duì)象谨履,而每一個(gè) DexFile 對(duì)象會(huì)對(duì)應(yīng)一個(gè) DexCache 對(duì)象欢摄。DexCache 的作用是用來(lái)緩存包含在一個(gè) dex 文件里面的類型 (Type), 方法 (Method), 域 (Field), 字符串 (String) 和靜態(tài)儲(chǔ)存區(qū) (Static Storage) 等信息。
art/runtime/mirror/dex_cache.cc
void DexCache::Init(const DexFile* dex_file,
ObjPtr<String> location,
StringDexCacheType* strings,
uint32_t num_strings,
TypeDexCacheType* resolved_types,
uint32_t num_resolved_types,
MethodDexCacheType* resolved_methods,
uint32_t num_resolved_methods,
FieldDexCacheType* resolved_fields,
uint32_t num_resolved_fields,
MethodTypeDexCacheType* resolved_method_types,
uint32_t num_resolved_method_types,
GcRoot<CallSite>* resolved_call_sites,
uint32_t num_resolved_call_sites)
REQUIRES_SHARED(Locks::mutator_lock_);
上面是 DexCache 的初始化函數(shù), num_resolved_fields 表示 DexCache 中緩存 Field 的個(gè)數(shù), 來(lái)打印一下這個(gè)參數(shù)
N:
03-21 15:57:56.409 5982 5982 E art : syh DexCache::Init classes.dex num_resolved_fields 56285
03-21 15:57:56.433 5982 5982 E art : syh DexCache::Init classes.dex num_resolved_fields 25449
03-21 15:57:56.437 5982 5982 E art : syh DexCache::Init classes.dex num_resolved_fields 63062
03-21 15:57:56.569 5982 5982 E art : syh DexCache::Init classes.dex num_resolved_fields 7802
03-21 15:57:56.917 5982 6097 E art : syh DexCache::Init classes.dex num_resolved_fields 115
03-21 15:57:58.088 5982 6032 E art : syh DexCache::Init classes.dex num_resolved_fields 16
03-21 15:57:58.993 5982 6121 E art : syh DexCache::Init classes.dex num_resolved_fields 27
O:
03-17 14:31:41.834 5358 5358 E zygote : syh DexCache::Init classes.dex num_resolved_fields 1024
03-17 14:31:41.854 5358 5358 E zygote : syh DexCache::Init classes.dex num_resolved_fields 1024
03-17 14:31:41.860 5358 5358 E zygote : syh DexCache::Init classes.dex num_resolved_fields 1024
03-17 14:31:42.051 5358 5358 E zygote : syh DexCache::Init classes.dex num_resolved_fields 1024
03-17 14:31:42.302 5358 5436 E zygote : syh DexCache::Init classes.dex num_resolved_fields 279
03-17 14:31:42.448 5358 5469 E zygote : syh DexCache::Init classes.dex num_resolved_fields 115
03-17 14:31:42.914 5358 5541 E zygote : syh DexCache::Init classes.dex num_resolved_fields 16
03-17 14:31:44.516 5358 5492 E zygote : syh DexCache::Init classes.dex num_resolved_fields 27
Android O 上, 一個(gè) DexCache 最多緩存 1024 個(gè) Field, 而實(shí)際上有上萬(wàn)個(gè) Filed, 導(dǎo)致 MterpGetStatic 的時(shí)候 cache 命中率極低, 最終導(dǎo)致 MterpGetStatic 耗時(shí)笋粟。
3.解決問(wèn)題
嘗試調(diào)整cache size與N一致怀挠,MterpGetStatic 明顯改善, 單一個(gè) inflate 就快了 80 ms, 優(yōu)化后高德地圖的啟動(dòng)時(shí)間可以減少 166 ms。