启动耗时分析(四)-具体方法耗时分析

原创文章,转载请注明出处,多谢!

如果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:

启动耗时分析(四)-具体方法耗时分析_第1张图片

Android O:

启动耗时分析(四)-具体方法耗时分析_第2张图片

发现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
启动耗时分析(四)-具体方法耗时分析_第3张图片

通过 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;
}
启动耗时分析(四)-具体方法耗时分析_第4张图片

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。

启动耗时分析(四)-具体方法耗时分析_第5张图片

你可能感兴趣的:(启动耗时分析(四)-具体方法耗时分析)