【性能优化实战】宝宝知道小程序FMP优化实录
背景
宝宝知道小程序从首次发布至今,经过了几十个版本的迭代。随着业务发展,页面功能内容的不断增多,相关性能数据不断变差,核心性能数据 FMP 长期处在 2000ms 以上。
在该项目之前,我们团队也对小程序做了一定的性能调优工作,内容包括:
- 包体积优化,去除了不少引用在项目中的图片素材文件,将包体积优化至 500kb 以下;
- 联合后端对耗时较高的业务接口做优化,单个接口返回速度需要控制在 100ms 左右;
- 优化了部分业务逻辑,小程序启动时减少了一些不必要的操作逻辑;
- 使用了小程序框架提供的最新生命周期 onInit ,可提前 100ms 左右发起业务网络请求;
- 使用 prelink 预连接网络,提升数据接口的请求效率。
经过上述手段之后,FMP 降到了 1900ms 左右,后续再也无法产生优化效果。
以上优化手段,基本排除了网络连接,包体积优化不到位引起的性能不佳。那么我们就只有一个问题需要仔细排查 —— 内容的渲染效率。
问题发现
目前从手百上打开宝宝知道小程序的最大入口页面为问答页,整体 pv 占比超过 6 成,那么我们优先优化这个页面,便可以带来性能收益的最大化。
通读问答页代码,按显示顺序从上到下,整个页面的功能点依次为:
- 直播信息横条
- 问题区
- 回答区
- 广告组件区
- 为你推荐 feedlist
需要展现的内容类别很多,内容信息量较为庞大。部分内容需要单独接口获取,外加上引入的广告组件,展现效率完全无法优化。
因为以上业务内容的展现需要,在加载时,使用 setData 触发内容渲染,会造成较大问题,比如:
- 加载期间调用 setData 的频次过多,onLoad 时会 set 、onShow 时会 set ,不同阶段发起的异步数据加载后也会 set 。当前线程内同时的多次 setData ,极易造成小程序渲染线程拥塞,影响内容渲染效率。
- 单次 setData 数据量过多,接口数据返回后,所有页面内需要的数据都一次性被提交到渲染线程中渲染,导致线程等待时间长,影响了有效内容的最终展现。虽然减少 setData 调用次数是官方提倡的,但是单次提交过多数据渲染,也并不是最优的策略。
以上两条 setData 的使用问题,在配置较好的手机设备上,并不会体现出问题,但是对于中低配置的手机设备,因为操作拥塞或大量数据渲染操作带来的渲染延迟,造成的用户体验损失还是很大的。
优化前的问答页数据渲染示意图
优化之前,页面加载完数据之后的首次渲染,会一次提交问题区、回答区、广告组件区三个部分的渲染任务,由于这三个区域涉及的内容量比较大,基本都会超过一屏,甚至两屏以上,另外各个区域也都包含一些图文内容,加上本身耗时较高的广告组件。整体页面内容渲染速度很差。并且,因为存在直播信息横条等单独异步请求加载的数据内容渲染,也容易造成 setData 操作在小程序渲染线程中拥塞现象的发生。
所以,从小程序 FMP 的统计规则来看,目前的数据渲染逻辑,显然并不是最优的。
既然 FMP 主要统计的是用户第一眼可以看到的首屏位置内容,那么我们是不是可以换个思路来完成我们的内容渲染工作。
在确保数据接口性能已经符合常规标准的情况下,我们可以使用更聪明的渲染策略。
优化方案
为了解决上述问题,我们构思了一套分屏式内容渲染策略,意在让用户能最快速度的先看到一部分关键内容,再分阶段渲染剩下需要被渲染的数据,而那些不需要被自动渲染的数据,可以改成由用户某种行为(比如滑动页面)触发加载和渲染。
优化后的问答页渲染示意图
PS:广告组件本身为异步组件,第二次 setData 会触发广告组件渲染,而广告组件内部自行发起异步内容的加载。
优化后的问答页渲染逻辑,整体上被拆分为四个阶段:
- 核心内容快速渲染阶段。该阶段为 FMP 主要检测的数据渲染时长,所以在这个阶段,我们需要让页面的内容和元素,足够装满一屏。
- 核心内容补全渲染阶段。该阶段将核心内容中存在的耗时内容,比如图片、视频以及小程序 native 组件等内容渲染上屏(注:关于渲染比较耗时的组件,目前已知视频 video 、所有小程序 native 组件,都不适宜放在第一阶段直接渲染,图片 image 如果条件允许,也尽量不放在第一屏)。
- 后续内容渲染阶段。该阶段将本次接口返回的需要渲染的数据全部上屏。
- 其他非主要异步数据渲染阶段(图例中的直播信息横条)。将另外一个接口的数据渲染上屏。
PS:如果存在核心内容渲染完成后依旧无法撑满一屏的情况,可以考虑设置整体页面 min-height:100vh ,或者页面下方放置占位元素,来达到撑满一屏的效果。
优化成果
该优化版于2020年8月4日上午11点左右全量上线,在手百中逐步放量。 FMP 指标在8月5日和6日两天快速下降,7日逐步稳定。总计优化 FMP 指标 540ms 。
从数据表现来看,优化效果非常明显。
并且,问答页作为宝宝知道小程序 pv 最大的落地页,占据总 pv 的 60% 左右,另外还有 40% 的其他页面需要我们持续优化,未来数据表现还有不小的优化空间。
工具建设
工欲善其事必先利其器。后续我们还需要优化其他入口页面的性能,以及为后续开发高性能页面做持续的技术储备,所以我们将开发中遇到的和性能有关的问题做了一些抽象,通过打造基础操作的工具类库,从底层上来解决或者规避问题。
上文中有提到,同时发起多个 setData 操作,极易造成小程序渲染线程的拥塞,导致渲染效率受到影响,降低小程序内容上屏的效率。实际开发中,我们如果要避免同时发起多个 setData ,必然会带来额外的逻辑思考成本和代码结构调整的成本,也容易因为调整,降低代码的可读性和可维护性。为了兼顾渲染性能的需要和代码结构的可读性,以及代码观感,我们专门设计了一个内容渲染任务管理器。
DataSetter
DataSetter 目前已经集成在团队内部的小程序工程脚手架中,通过 AdvancedPage 创建的小程序 Page 实例,即可支持通过该管理器开放的 api 接口,向小程序的渲染线程提交数据渲染任务。
DataSetter 将小程序 setData 操作封装为一个队列式的渲染任务管理器,使用 DataSetter 进行 set 数据操作,可以使得单位时间内只有一个 setData 操作被执行,而其他被同时 set 的数据,将在队列中排队依次执行。
图例:优化前同时 setData ,会导致小程序渲染线程的拥塞
图例:优化后同时 set ,DataSetter 会整体管理数据渲染任务,不会造成渲染线程拥塞
为了支持分屏式渲染策略的编写,DataSetter 的 API 被设计为链式调用式设计。可以以非嵌套的方式编写N阶段内容渲染逻辑,代码行文清晰易懂。
this.$dataSetter.set({ // 第一阶段渲染数据 status:'success', aaa:111 }).done(e => { // 第一阶段渲染完成 console.log('第一阶段渲染完成'); }).set({ // 第二阶段渲染数据 bbb:222 }).set({ // 第三阶段渲染数据 ccc:333 }).done(e => { // 第三阶段渲染完成 console.log('第三阶段渲染完成‘); });
DataSetter 源码
/** * @name DataSetter * @description setData语法增强,支持链式调用和队列式set数据,一次setData成功之后才开始下一次setData */ class DataSetter { queue = []; context = null; index = 0; constructor(context) { this.context = context; } set(dataset = {}) { this.queue.push({dataset, callback: null}); if (this.queue.length === 1) { this.exec(); } return this; } done(callback) { this.queue[this.queue.length - 1].callback = callback; return this; } exec() { let task = this.queue[this.index]; if (!task) { // console.log('all task done!'); this.refresh(); return; } const next = () => { // console.log(set data ${this.index} ok!); task.callback && task.callback(); this.index++; this.exec(); }; // 如果当前任务dataset为空,则不调用原生setData,直接执行回调 if (!task.dataset || Object.keys(task.dataset).length < 1) { next(); return; } // console.log(set data ${this.index}); this.context.setData(task.dataset, next); } refresh() { this.queue = []; this.index = 0; } } // Page Demo Page({ $dataSetter: null, onLoad() { this.$dataSetter = new DataSetter(this); } });
造成小程序性能不理想的情况有很多,而渲染问题的解决和优化是可以带来最大收益的,并且如果能根据实际的业务场景,来灵活设计视图的渲染策略,往往可以带来奇效。渲染问题优化是一件非常精细的事情,尤其是面对逐渐复杂的业务代码,敢于去改造尝试,才是最终成功的起点。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
滴滴数据通道服务演进之路
桔妹导读:滴滴数据通道引擎承载着全公司的数据同步,为下游实时和离线场景提供了必不可少的源数据。随着任务量的不断增加,数据通道的整体架构也随之发生改变。本文介绍了滴滴数据通道的发展历程,遇到的问题以及今后的规划。 1. 背景 数据,对于任何一家互联网公司来说都是非常重要的资产,公司的大数据部门致力于解决如何更好的使用数据,挖掘数据价值,而数据通道服务作为“大数据”的前置链路,一直以来都在默默的为公司提供及时,完整的数据服务,这里我们对滴滴数据通道的演进做一个全面的介绍。 2. 数据通道简介 数据通道服务,顾名思义,是数据的通路,负责将数据从A同步到B的一套解决方案。 异构数据的同步是公司很多业务的普遍需求,通道服务也就成为了一项基础服务。包括但不限于日志,Binlog同步到下游各类存储和引擎中,如HIVE,ES,HBase等,用于报表,运营等场景。 数据通道方案本身涉及的组件很多,链路也比较复杂,这里通过一个简化的有向图来介绍下通道的核心流程。 有向图的顶点表示存储,包括磁盘,消息队列以及各种存储服务,边和方向表示数据流量,而数据流动的动力则是边上的各个同步引擎。 仅从图中的链路可以看出...
- 下一篇
当 Widget 遇到智能化
作者: kAzec, iOS 开发者,目前就职于字节跳动 Sessions: https://developer.apple.com/videos/play/wwdc2020/10194/ 在阅读本文前,推荐先对新的 Widget 系统有个大致的了解,同时也推荐先熟悉 Apple 在 SiriKit 中引入的 Intents API。 概述 在 WWDC 2020 中,Apple 引入了 Widget (小挂件)这一全新的 App Extension,允许开发者在设备主屏幕、“今天”视图和 macOS 通知中心上显示自定义的小挂件。 该 session 首先介绍了如何通过 Intents API,让我们开发的 Widget 支持让用户进行个性化配置,并介绍了目前支持的配置参数的类型,如何自定义参数类型,并且支持为用户动态生成待选项列表。 之后介绍了 Widget 支持在 Smart Stack 中堆叠展示,同时可以通过开发者的配置让 Widget 智能地被系统展现: 通过复用 Intents 的“捐赠(donate)”的概念,让用户在合适的时机看到 TA 需要的内容。 通过为特定的 T...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Hadoop3单机部署,实现最简伪集群
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7设置SWAP分区,小内存服务器的救世主
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长