原创文章,转载请注明出处,多谢!
如果cpu频率、调度 和 compiler filter都一一排除了,没问题。那接下来就看是否有具体方法耗时。
一、常用的分析手段:
1.systrace
这里可按systrace中各个阶段来逐段对比分析,当然这里也分冷热启。
冷启动可以拆分如下若干阶段:
deliver input / fork process / bind application / activity start / doFrame / drawFrame / SF invalidate&refresh
热启动就主要考虑绘制和渲染了。
看是否差距集中在一个某个阶段内,如果是特定区域的差异那么就来针对具体方法耗时进行分析。
找到有差异的阶段可以通过加trace,来缩小范围和细化具体方法。
各层加trace的方式:
APP:
Trace.beginSection("");
Trace.endSection();
注:抓systrace的时候需要指定对应的app进程。
系统java层:
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate”);
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
注:前面的tag参数对应的抓systrace的开关选项。
Native:
#define ATRACE_TAG ATRACE_TAG_ALWAYS
#include // for c++
#include // for c
ATRACE_CALL();
or
ATRACE_BEGIN("");
ATRACE_END();
2. TraceView or AS profile cpu
对于app的问题,可以借助traceView 或者 as profile cpu检查 CPU Activity 和函数跟踪来帮助定位耗时方法,不断缩小范围来定位问题。
3. 反编译
对于三方app,没有源码,可以通过oatdump反编译来分析:
adb shell oatdump --oat-file=/data/app/包名/oat/arm64/base.odex > demo.txt
字节码命令说明
二、实战举例:高德地图耗时分析
1.发现问题
在cpu频率、调度 和 compiler filter都一一排除了的前提下,通过systrace来分析具体启动各阶段耗时情况。
Android N:
Android O:
发现MapContainer执行时间比较长,MapContainer 是高德自己实现的类,应该是高德自己的实现方式在不同 Android 版本上差别比较大。因为高德是三方应用,没有代码,所以借助traceView来分析。
注:如果是冷启动可以使用命令行来抓取:
1)启动指定 Activity 同时启动 trace
am start -n com.stan.note.newdemo/.MainActivity --start-profiler /data/local/tmp/test.trace
am profile com.stan.note.newdemo stop
2)启动指定 Activity 同时启动 trace, 自动结束
am start -n com.stan.note.newdemo/.MainActivity -P /data/local/tmp/test.trace
通过 TraceView 发现有两个相关的方法非常耗时:
com.autonavi.mao.core.OverlayManager.init ()V
com.autonavi.minimap.commute.CommuteOverlay.init ()V
下面通过oatdump来反编译这两个方法
截取一个小片段:
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指令中对应的函数并加上trace,来确定是哪个具体函数耗时。
比如sget指令,对应到如下解释器解释指令
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
这部分是汇编指令,具体指令执行不耗时,肯定是函数耗时。函数调用$helper 对应MterpGet32Static函数。
在对应的函数处加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(field_idx,
new_value,
referrer,
self,
&ArtField::SetInt);
}
在对应MterpSetStatic方法加trace.
template
ALWAYS_INLINE return_type MterpGetStatic(uint32_t field_idx,
ArtMethod* referrer,
Thread* self,
return_type (ArtField::*func)(ObjPtr))
REQUIRES_SHARED(Locks::mutator_lock_) {
ScopedTrace trace(__FUNCTION__);
return_type res = 0; // On exception, the result will be ignored.
ArtField* f =
FindFieldFromCode(field_idx,
referrer,
self,
primitive_type);
if (LIKELY(f != nullptr)) {
ObjPtr obj = f->GetDeclaringClass();
res = (f->*func)(obj);
}
return res;
}
2.分析问题
MterpGetStatic 就是去获取一个类的静态成员, 为什么会用掉 85 ms ?apk中dex文件会在art中转化为一个DexFile对象,而每一个 DexFile 对象会对应一个 DexCache 对象。DexCache 的作用是用来缓存包含在一个 dex 文件里面的类型 (Type), 方法 (Method), 域 (Field), 字符串 (String) 和静态储存区 (Static Storage) 等信息。
art/runtime/mirror/dex_cache.cc
void DexCache::Init(const DexFile* dex_file,
ObjPtr 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* resolved_call_sites,
uint32_t num_resolved_call_sites)
REQUIRES_SHARED(Locks::mutator_lock_);
上面是 DexCache 的初始化函数, num_resolved_fields 表示 DexCache 中缓存 Field 的个数, 来打印一下这个参数
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 上, 一个 DexCache 最多缓存 1024 个 Field, 而实际上有上万个 Filed, 导致 MterpGetStatic 的时候 cache 命中率极低, 最终导致 MterpGetStatic 耗时。
3.解决问题
尝试调整cache size与N一致,MterpGetStatic 明显改善, 单一个 inflate 就快了 80 ms, 优化后高德地图的启动时间可以减少 166 ms。