Flutter卡顿问题的监控与思考

发表于 4年以前  | 总阅读数:3425 次

背景

使用Flutter技术构建的应用,一直以高性能高流畅度著称。但是随着应用复杂度越来越高,Flutter会出现一些页面流畅度明显低于Native的情况,甚至可能发生一些卡顿。而很多时候卡顿都发生在线上,即使获得了用户的操作路径,也难以重现。如果我们有一套卡顿监控系统,能够帮助我们捕获到卡顿时的堆栈,那么在发生卡顿的时候,我们就可以定位到具体是哪个函数引起的卡顿,从而解决这些问题。

既然想要设计一个卡顿监控系统,那么我们就需要先解决两个问题:

  • 如何判断当前发生了卡顿。
  • 如何在卡顿时获取堆栈。

如何判断卡顿

既然我们希望能够抓取Flutter的卡顿堆栈,那么首先我们得先有办法判断Flutter App是否发生卡顿。为此,我们先来简单回顾一下Flutter的渲染原理。Flutter的UI Task Runner负责执行Dart代码,而Flutter的渲染管线也是在UI Task Runner中运行的。每次Flutter App的界面需要更新时,Framework会通过ui.window.scheduleFrame通知Engine。然后Engine会注册一个Vsync信号的回调,在下一个VSync信号到来之际,Engine会通过ui.window.onBeginFrame和ui.window.onDrawFrame回调给Framework来驱动Flutter渲染管线,渲染管线中的Build、Layout、Paint一一被执行,生成了最新的Layer Tree。最后Layer Tree通过ui.window.render发送到了Engine端,交给GPU Task Runner做光栅化与上屏。

我们可以定义一个卡顿阈值,在ui.window.onBeginFrame开始计时,在ui.window.onDrawFrame做好卡口,如果渲染管线的执行时间过长,大于卡顿阈值,那么我们就可以判断发生了卡顿。

如果等到我们判断出了当前发生了卡顿,再去采集堆栈,为时已晚。因此,我们需要另外起一个Isolate,每隔一小段时间就去采集一次root Isolate的堆栈,那么当我们判断出现卡顿时,只要将从卡顿开始到卡顿结束的这段时间采集到的堆栈做一次聚合,就能知道是哪个方法引起了卡顿了。

举个例子,如果我们定义的卡顿阈值为100ms,然后每隔5ms采集一次卡顿堆栈,假设ui.window.onBeginFrame开始到ui.window.onDrawFrame结束总共耗时200ms,其中foo方法耗时160ms,bar方法耗时30ms,其余方法耗时10ms。那么在这段时间,我们一共能采集到40个堆栈,其中有32个堆栈的栈顶为foo方法,6个堆栈的栈顶为bar方法。在堆栈聚合后,我们就能发现foo方法的耗时大约为160ms,而bar方法的耗时大约为30ms。

这个方案看上去比较简单,整体思路上也是借鉴了Android端的卡顿检测框架BlockCanary,那么这个方案是否可行呢?我们需要解决的第一个问题,就是如何在另一个Isolate去采集root Isolate的堆栈。

堆栈采集方案一:修改Dart SDK在Dart中,我们可以通过调用StackTrace.current来获取当前Isolate的调用栈。那么它能否帮助我们获取卡顿时候的堆栈呢?非常可惜,它做不到这一点。

举个例子:假设我们有一个名叫foo的方法,耗时大概300ms,在root Isolate中执行,很明显,这个方法会引起卡顿。而StackTrace.current并不能获取帮助我们定位到这个耗时的foo方法。我们需要的,是在另一个Isolate中采集到root Isolate堆栈的方法。

官方的相关issue

并不是只有我们有这个诉求,google的同学在flutter的repo下,提了Issue:Add API to query main Isolate's stack trace #37204。这个Issue的大致内容是说,希望Dart能够提供一个API,用于在另一个Isolate去采集main Isolate堆栈,当前这个Issue还是open的状态。

Issue提出到现在大概已经过去一年时间了,为什么这个API还是没有实现呢?其实,实现这个API本身并不困难,只是官方有一些自己的考量,其中之一就是这可能会引入安全性问题:Dart Isolate之间本应该相互隔离,如果添加了这个API,那么可能会有黑客通过多次调用该API来获取大量的堆栈信息,再通过比对这些堆栈的差异来对加密秘钥发起定时攻击等。看来官方短期之内是不会提供这个API了,那么我们是不是可以先试试通过修改Dart SDK来实现类似的功能。

通过修改SDK获取API

我们先来看看StackTrace.current是如何获取堆栈的吧

我们可以看到,StackTrace.current方法的修饰符中有一个external,这代表了这是一个external函数,Dart中的external函数意味着这个函数的声明和实现是分开的,这里只是声明,实现在另一个地方,其实现的地方如下:

从StackTrace.current的实现中有一个native关键字,native关键字是Dart的Native Extension的关键字,意味着这个方法是C/C++实现的。Native Extension与Java中的JNI非常的相似。

我们终于找到了实现,CurrentStackTrace,通过观察发现,它的第一个参数是一个thread。可见CurrentStackTrace方法获取的堆栈是基于thread的,那么是不是说,如果我们在另一个Isolate中,将root Isolate对应的Thread作为参数,传入到CurrentStackTrace方法里,就能获得root Isolate对应的堆栈了呢?

为了验证我们这个想法,我们新增了两个方法:StackTrace.prepare和StackTrace.root,我们在root Isolate 中调用StackTrace.prepare,将root Isolate的thread对象使用静态变量rootIsolateThread保存起来。StackTrace.prepare对应的C++实现如下

然后我们新开一个Isolate,在这个新的Isolate中,我们调用StackTrace.root来获取root Isolate的堆栈,StackTrace.root对应的C++实现如下

经过验证发现,通过这个方案,的确能在另一个Isolate中获取root Isolate的堆栈。当然上面的修改主要还是为了验证可行性,如果真的要采用修改Dart SDK的方案,还有非常多的地方需要考虑。

修改Dart SDK的这个方案大大增加了后期的维护成本,有没有可能存在一种不修改Dart SDK,还是能获取到堆栈的方案呢?

堆栈采集方案二:AOT模式下采集堆栈(暂停线程)

在不修改Dart SDK的前提下获取堆栈,听上去感觉是一个不可能完成的任务。但是有时候我们遇到了问题,或许转变一下思路,就能找到答案。

AOT模式与符号表

让我们一起来梳理一下我们的诉求,首先我们设计的是一个线上卡顿监控方案,这个场景下的Dart代码是基于AOT编译的,在iOS端其产物为App.framework,在Android端则为libapp.so。基于AOT,也就意味着Dart代码(包括SDK和你自己的)会被编译成平台相关的机器码。

那么Dart语言AOT编译生成的可执行程序与C语言编译生成的可执行程序,是否有区别呢?从操作系统的角度来看,它们并没有什么本质区别。操作系统只关心这个可执行程序如何加载,如何执行,至于程序是从C语言还是Dart语言编译过来的,它并不关心。

我们先来把目光聚焦到Dart代码在iOS端profile模式下的产物App.framework。从iOS的视角触发,这是一个Embedded Framework。我们可以使用nm命令导出其符号表,以下是符号表的一部分:

我们惊喜地发现,这些符号与Dart函数几乎是一一对应。比如符号PrecompiledElementupdate_260,很明显对应的Dart函数为Element.update。

有了这份符号表,也就意味着,如果我们能采集到root Isolate对应线程的native的堆栈,我们就可以通过符号化来还原出当时Dart函数的调用栈。而且我们也不再需要去寻找从另一个Isolate获取root Isolate的Dart堆栈的方法了。与之对应的,我们只需要能够在另一个线程获取root Isolate对应的线程的native堆栈即可。

堆栈采集的方案

栈帧采集的方案整体思路如下:

  1. 获取当前进程中的所有线程,并找到Flutter UI TaskRunner对应的线程
  2. 暂停该线程,并获取该线程当前的寄存器的值,重点为PC和FP
  3. 根据栈帧回溯算法,获取堆栈
  4. 让该线程继续运行
  5. 在当前进程或者远端做符号化

方案实现

接下来我们来看看如何实现这个方案,我们以iOS端为例子,来说明如何实现这个方案:

在iOS端,我们可以通过API task_threads来获取所有的线程,代码如下:

我们可以通过比对线程名字来定位到UI Task Runner对应的线程,如果是Flutter单Engine方案,那么UI Task Runner对应的Thread的名字应为"io.flutter.1.ui"。

在采集堆栈前,我们得先暂停这个线程。

暂停线程后,我们就可以通过 thread_get_state去获取这个线程此时此刻的寄存器的值了,其中能够帮助我们做栈帧回溯的两个寄存器分别是pc和fp,我们这里的代码是以arm64为例子的,在实际的产品中,还需要考虑到其他的架构:

获取pc和fp后,就可以进行栈帧回溯了。至于如何进行栈帧回溯,我们会在下一个小节单独说明。栈帧采集完之后,我们需要让线程继续运行:

以上就是iOS端堆栈采集方案的大体实现了。Android端想实现这个方案,思路上大同小异,无论是找到所有的线程,定位到UI Task Runner对应的线程,还是线程的暂停和恢复,都能找到解决方案。唯一比较麻烦的地方在于如何获取另一个线程暂停时的寄存器的值,这部分可以使用ptrace来完成,不过这个需要起一个独立的进程。

栈帧回溯原理

上文说到,我们获得了pc和fp寄存器的值,该如何做栈帧回溯呢?

这里我们以ARM64栈帧布局为例子(也就是上图)。每次函数调用,都会在调用栈上,维护一个独立的栈帧,每个栈帧中都有一个FP(Frame Pointer),指向上一个栈帧的FP,而与FP相邻的LR(Link Register)中保存的是函数的返回地址。也就是我们可以根据FP找到上一个FP,而与FP相邻的LR对应的函数就是该栈帧对应的函数。回溯的算法如下

堆栈采集完毕后,我们只需要将采集到的堆栈进行符号化即可。

堆栈采集方案三:AOT模式下采集堆栈(通过信号)

性能瓶颈

上面的这个方案可能会对性能造成一些影响,堆栈回溯本身并不耗时,真正的耗时在于线程的暂停和恢复。线程暂停后,线程就会进入阻塞状态,而去恢复线程时,线程并不会立即执行,而是会进入就绪状态,等待内核调度为其分配CPU时间片。所以在这个方案,每一次采集线程堆栈,都意味着这个线程的状态可能会从运行态到阻塞态再到就绪态。

那么有没有更为轻量级的采集堆栈的方案?

信号机制原理

信号(Signal)是事件发生时对进程的通知机制,有时候也称之为软件中断。一般发给进程的信号,通常是由内核产生的,比如访问了非法的内存地址,或者被0除等等,当然,一个进程可以向另一个进程或者自身发送信号。如果进程注册了信号处理器(Signal Handler),那么当收到信号后,就会中断当前程序,开始调用信号处理器程序,等信号处理器程序执行完成后,当前程序会在被中断的位置继续执行。

新方案的实现

我们先注册一个信号处理器,用于采集堆栈。接着,我们还是启动一个采集线程,每隔一段时间,向UI Task Runner发送一个信号。当收到信号后,UI Task Runner对应的线程就会被中断,执行信号处理器程序来采集堆栈,堆栈采集完后,程序会从中断点回复执行。

我们来看看这个方案具体如何实现,这次我们以Android端为例子:

首先我们先注册一些signal handler,用于在收到信号时采集堆栈

接着我们每隔一段时间,就向UI Task Runner对应的线程发送一个信号。

信号到达后,该线程就会中断当前执行的程序,然后调用signal handler采集堆栈,其中signalHandler的实现如下

实际上,FaceBook的性能监控方案profilo,以及Dart VM的CPU Profiler,均使用了这个方案来采集堆栈。

堆栈采集方案对比

我们来对比一下上面提到的3个方案,它们的区别如下图所示:


我们可以看到,方案三无需修改SDK,所以维护成本较低,并且在三个方案中它的性能损耗是最低的。最终我们决定采用方案三来作为我们堆栈采集的方案。

总结

本文主要介绍了我们在设计Flutter卡顿监控系统的一些思路,给出了如何判断卡顿跟如何获取堆栈的思考和探索,目前这个方案的产品化正在进行当中。Flutter作为高性能的跨平台方案,其渲染性能从理论上来说,可以做到不弱于原生。同时Flutter在性能体验方向上,和原生相比,还有非常多值得探索的地方,让我们一起不忘初心,继续朝着这个方向前进。

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
如何有效定位Flutter内存问题? 4年以前  |  16302次阅读
鸿蒙系统起飞!Flutter 完全适配指南 2年以前  |  12697次阅读
Flutter的手势GestureDetector分析详解 5年以前  |  12411次阅读
Flutter插件详解及其发布插件 5年以前  |  12139次阅读
 目录