Akindone's Studio.

Dart性能优化工具——Observatory

字数统计: 1.6k阅读时长: 5 min
2018/11/11 Share

Observatory 是用于分析和调试Dart应用程序的工具。Observatory允许您根据需要查看正在运行的Dart虚拟机(VM),并提供实时,即时的数据报告。您可以使用它来浏览应用程序的大多数状态。

打开Observatory

有2种方式:

  1. 在androidStudio中打开Flutter Inspector面板,点击小闹钟图标,如下图
    open observatory from AS
  2. 再命令行中运行flutter run,应用启动成功后,命令行中会输出一个url,把url copy到浏览器即可。
    open observatory from command line

打开Observatory面板,要先选择isolate,表示当前应用。
entey screen

主要页面

下面是性能优化常关注的几个页面。
main screen

CPU Profile

app的时间都花在哪了?

进入这个页面后要一般需加载个几秒钟,so be patient。图表的下部按cpu占用比例做了一个列表,反映的是函数的调用次数和执行时间(划重点)。一般排在前面的函数(这些函数是?有待学习)都不是我们写的dart代码。如果你发现自己的某个函数调用占比反常,那么可能存在问题。

注:flutter程序的cpu profile和官方文档上的数据展示不太一样,没有VM tags,所以对于百分比的具体含义有待研究。
cpu profile

采样过程:它每隔一定时间对isolate做采样,采样的数据存储在一个环形缓冲区(叫做profile),它能存放约2分钟的数据,一旦缓冲区满了,它会用最新的sample替换掉最旧的。

  • Profile contains:采样时长和对应的采样数
  • Sampling:采样频率,默认1000Hz,即每毫秒采样一次

Allocation Profile

内存都被谁吃了?

allocation profile

Heap

堆,动态分配的Dart对象所在的内存空间

  • New generation: 新创建的对象,一般来说对象比较小,生命周期短,如local 变量。在这里GC活动频繁
  • Old generation:从GC中存活下来的New generation将会提拔到老生代Old generation,它比新生代空间大,更适合大的对象和生命周期长的对象

通过这个面板你能看到新生代/老生代的内存大小和占比;每个类型所占用的内存大小。

为了debug的方便,我们可以获取到某段时间的内存分配情况:点击Reset Accumulator按钮,把数据清零,执行一下要测试的程序,点击刷新。

为了检查内存泄露,我们可以点击GC按钮,手动执行GC。

Accumulator Size:自点击Reset Accumulator以来,累加对象占用内存大小
Accumulator Instances:自点击Reset Accumulator以来,累加实例个数
Current Size:当前对象占用内存大小
Current Instances:当前对象数量

Heap Map

是否出现内存碎片化

heap map 面板能查看old generation中的内存状态

它以颜色显示内存块。 每个内存页面(page of memory)为256 KB,每页由水平黑线分隔。 像素的颜色表示对象的类ID - 例如,蓝色表示字符串,绿色表示双精度表。 可用空间为白色,指令(代码)为紫色。 如果启动垃圾收集(使用“分配配置文件”屏幕中的GC按钮),堆映射中将显示更多空白区域(可用空间)。
将光标悬停在上面时,顶部的状态栏显示有关光标下像素所代表的对象的信息。 显示的信息包括该对象的类型,大小和地址。
当你看到白色区域中有很多分散的其它颜色,说明存在内存碎片化,可能是内存泄露导致的。

其它

Code Coverage

知道哪些代码执行了,哪些没有执行

code coverage

  • 绿色:已执行的代码
  • 红色:未执行的代码
  • 没有颜色:不可执行的代码

应用场景:写某个类的单元测试,跑完测试后,可以查看哪些代码没有覆盖到,进而补全

Instance 信息

查看某个实例的状态,如获取redux状态树

watch state

Observatory中的Tags体系

Observatory用Tag来区分不同的执行类型,让你能快速了解这个isolate主要有哪些行为。它包括以下2类

VM Tags

预设tag,代表VM activities,除了Dart这个Tag,其它是我们无法直接控制的,比如垃圾回收。

  • CompileOptimized, CompileScanner, CompileTopLevel, CompileUnoptimized: 编译Dart code
  • GCNewSpace: 垃圾回收新生代内存(new generation)
  • GCOldSpace: 垃圾回收老生代内存(old generation)
  • Idle:表示当前isolate没有事情可做,它本身不是一个VM的行为,比如它正在等异步I/O,或者已经执行完成,在等待被释放的一种静止状态。
  • Native:执行native代码
  • Runtime:执行runtime代码
  • Dart:执行Dart代码
  • VM:启动一个isolate,它也包括上面列出tag外的执行行为

User Tags

上面 Dart tag 只能告诉你相比与其它VM activities, Dart代码占用cpu资源的比例,但想更细致的了解 Dart code 中是否有性能瓶颈就需要自定义tag了,你可以用dart:developer这个库去创建自定义tag。

1
2
3
4
5
6
7
8
9
10
11
import 'dart:developer';

var customTag = new UserTag('MyTag');

// Save the previous tag when installing the custom tag.
var previousTag = customTag.makeCurrent();

// your code here

// Restore the previous tag.
previousTag.makeCurrent();

上面的代码自定义了一个User tag:MyTag,你可以cpu profile面板里看到它的执行比例,需要注意的一点是:当前active状态的tag只能有一个,所以需要先stash之前的tag,当代码段执行完,再restore之前的tag

Stub Code

在cpu profile tree 中有时你能看到一个[stub]标记,它是一些共用的代码段,执行一些比机器码更高级的操作,如方法寻址和分配内存,已编译段Dart code和C++运行时代码间的过度。如果你发现stub占了较高的比例,说明项目花了较多的时间在runtime 或者 unoptimized code。

  • InlineCacheStub / SubtypeTestCacheStub
    函数在第一次执行时,它是无优化编译。在做调用和类型检查时会缓存方法寻址和类型测试的结果,这样可以避免一些重复操作。但如果之类代码占比较高,说明你但程序花了比较多时间在未优化的代码,比如执行了许多不同的代码,其频率较低,不会触发优化。

参考

CATALOG
  1. 1. 打开Observatory
  2. 2. 主要页面
    1. 2.1. CPU Profile
    2. 2.2. Allocation Profile
      1. 2.2.1. Heap
    3. 2.3. Heap Map
  3. 3. 其它
    1. 3.1. Code Coverage
    2. 3.2. Instance 信息
    3. 3.3. Observatory中的Tags体系
      1. 3.3.1. VM Tags
      2. 3.3.2. User Tags
      3. 3.3.3. Stub Code
  4. 4. 参考