啟動耗時分析(四)-具體方法耗時分析

原創(chuàng)文章,轉(zhuǎn)載請注明出處,多謝!

如果cpu頻率、調(diào)度 和 compiler filter都一一排除了,沒問題。那接下來就看是否有具體方法耗時。

一、常用的分析手段:

1.systrace
這里可按systrace中各個階段來逐段對比分析,當(dāng)然這里也分冷熱啟。
冷啟動可以拆分如下若干階段:
deliver input / fork process / bind application / activity start / doFrame / drawFrame / SF invalidate&refresh
熱啟動就主要考慮繪制和渲染了。

看是否差距集中在一個某個階段內(nèi),如果是特定區(qū)域的差異那么就來針對具體方法耗時進(jìn)行分析。
找到有差異的階段可以通過加trace,來縮小范圍和細(xì)化具體方法。

各層加trace的方式:
APP:

Trace.beginSection("");
Trace.endSection();

注:抓systrace的時候需要指定對應(yīng)的app進(jìn)程。

系統(tǒng)java層:

Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate”);
Trace.traceEnd(Trace.TRACE_TAG_VIEW);

注:前面的tag參數(shù)對應(yīng)的抓systrace的開關(guān)選項。

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
對于app的問題,可以借助traceView 或者 as profile cpu檢查 CPU Activity 和函數(shù)跟蹤來幫助定位耗時方法,不斷縮小范圍來定位問題。

3. 反編譯
對于三方app,沒有源碼,可以通過oatdump反編譯來分析:
adb shell oatdump --oat-file=/data/app/包名/oat/arm64/base.odex > demo.txt
字節(jié)碼命令說明

二、實(shí)戰(zhàn)舉例:高德地圖耗時分析

1.發(fā)現(xiàn)問題

在cpu頻率、調(diào)度 和 compiler filter都一一排除了的前提下,通過systrace來分析具體啟動各階段耗時情況。

Android N:

Android O:

發(fā)現(xiàn)MapContainer執(zhí)行時間比較長,MapContainer 是高德自己實(shí)現(xiàn)的類,應(yīng)該是高德自己的實(shí)現(xiàn)方式在不同 Android 版本上差別比較大。因?yàn)楦叩率侨綉?yīng)用,沒有代碼,所以借助traceView來分析。

注:如果是冷啟動可以使用命令行來抓?。?/p>

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, 自動結(jié)束

am start -n com.stan.note.newdemo/.MainActivity -P /data/local/tmp/test.trace

通過 TraceView 發(fā)現(xiàn)有兩個相關(guān)的方法非常耗時:

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指令中對應(yīng)的函數(shù)并加上trace,來確定是哪個具體函數(shù)耗時。

比如sget指令,對應(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ù)調(diào)用$helper 對應(yīng)MterpGet32Static函數(shù)。

在對應(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>);
}

在對應(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.分析問題

MterpGetStatic 就是去獲取一個類的靜態(tài)成員, 為什么會用掉 85 ms ?apk中dex文件會在art中轉(zhuǎn)化為一個DexFile對象,而每一個 DexFile 對象會對應(yīng)一個 DexCache 對象。DexCache 的作用是用來緩存包含在一個 dex 文件里面的類型 (Type), 方法 (Method), 域 (Field), 字符串 (String) 和靜態(tài)儲存區(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 的個數(shù), 來打印一下這個參數(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 上, 一個 DexCache 最多緩存 1024 個 Field, 而實(shí)際上有上萬個 Filed, 導(dǎo)致 MterpGetStatic 的時候 cache 命中率極低, 最終導(dǎo)致 MterpGetStatic 耗時。

3.解決問題

嘗試調(diào)整cache size與N一致,MterpGetStatic 明顯改善, 單一個 inflate 就快了 80 ms, 優(yōu)化后高德地圖的啟動時間可以減少 166 ms。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 關(guān)于冷啟動的優(yōu)化方法,網(wǎng)上已經(jīng)有很多的文章了,總結(jié)起來,大概有以下幾種優(yōu)化方式: 優(yōu)化布局,這一步是最簡單的...
    事多店閱讀 5,279評論 1 11
  • 請保持淡定,分析代碼,記住:性能很重要。 啟動時間優(yōu)化 毫無疑問,應(yīng)用的啟動速度越快越好。 本文可以幫助你優(yōu)化應(yīng)用...
    Mupceet閱讀 11,969評論 5 19
  • 先講點(diǎn)題外話 簡述Activity的幾種啟動模式 standard標(biāo)準(zhǔn)啟動模式,也是Activity的啟動模式,以...
    大大大大大先生閱讀 6,902評論 0 3
  • 概要 應(yīng)用運(yùn)行時的卡頓問題非常影響用戶體驗(yàn),嚴(yán)重降低產(chǎn)品表現(xiàn)力,本文將介紹應(yīng)用卡頓原因以及分析方法等等。 卡頓問題...
    某昆閱讀 2,855評論 1 8
  • part2:重慶市區(qū) “在3D魔幻城市,我上演了自己的魔幻故事?!?-啊坨 Day6晚: 較場口夜市-八一好吃街-...
    啊坨閱讀 343評論 0 0

友情鏈接更多精彩內(nèi)容