【得物技术】时间切片的实践与应用

2024-01-11 10:18

本文主要是介绍【得物技术】时间切片的实践与应用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0x1:前言

每一个拥有【高级资深】title的前端工程师,必定会对项目的整体性能优化有自己的独到见解。这是往前端业务架构方向转变的必须要具备的能力之一。

本文就给大家介绍一个性能优化的手段之一:时间切片(Time Slicing)

根据W3C性能小组的介绍,超过50ms的任务就是长任务。

序号

时间分布

描述

1

0 to 16 ms

Users are exceptionally good at tracking motion, and they dislike it when animations aren't smooth. They perceive animations as smooth so long as 60 new frames are rendered every second. That's 16 ms per frame, including the time it takes for the browser to paint the new frame to the screen, leaving an app about 10 ms to produce a frame.

2

0 to 100 ms

Respond to user actions within this time window and users feel like the result is immediate. Any longer, and the connection between action and reaction is broken.

3

100 to 1000 ms

Within this window, things feel part of a natural and continuous progression of tasks. For most users on the web, loading pages or changing views represents a task.

4

1000 ms or more

Beyond 1000 milliseconds (1 second), users lose focus on the task they are performing.

5

10000 ms or more

Beyond 10000 milliseconds (10 seconds), users are frustrated and are likely to abandon tasks. They may or may not come back later.

表格内容摘抄自使用 RAIL 模型评估性能

根据上面的表格描述我们可以知道,当延迟超过100ms,用户就会察觉到轻微的延迟。所以为了解决这个问题,每个任务不能超过50ms。

为了避免当延迟超过100ms,用户就会察觉到轻微的延迟这种情况,我们可以使用两种方案,一种是Web Worker,另一种是时间切片(Time Slicing)

0x2:web worker

测试Demo代码在此

众所周知,JavaScript 语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。

针对我们业务上来讲,一旦我们执行了过多的长任务,执行过程很容易就被阻塞,出现页面假死的现象。虽然我们可以将任务放在任务队列中,通过异步的方式执行,但这并不能改变JS的本质。

所以为了改变这种现状,whatwg推出了Web Workers。

关于web worker,不需要深入,想了解的同学可以查看MDN - web worker

  1. Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。
  2. 线程可以执行任务而不干扰用户界面。
  3. 可以使用XMLHttpRequest执行 I/O (尽管responseXMLchannel属性总是为空)。一旦创建, 一个worker 可以将消息发送到创建它的JavaScript代码, 通过将消息发布到该代码指定的事件处理程序(反之亦然)。

Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。

Web Worker 有以下几个使用注意点。

  1. 同源限制:分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
  2. DOM 限制:Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用documentwindowparent这些对象。但是,Worker 线程可以navigator对象和location对象。
  3. 通信联系:Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。
  4. 脚本限制:Worker 线程不能执行alert()方法和confirm()方法,但可以XMLHttpRequest 对象发出 AJAX 请求。
  5. 文件限制:Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

我们可以看看使用了Web Worker之后的优化效果:

worker.js

self.onmessage = function () {const start = performance.now()while (performance.now() - start < 1000) {}postMessage('done!')
}

myWorker.js

const myWorker = new Worker('./worker.js')
setTimeout(_ => {myWorker.postMessage({})myWorker.onmessage = function (ev) {console.log(ev.data)}
}, 5000)

测试Demo代码在此, 有兴趣的小伙伴可以down下来学习

0x3:什么是时间切片

时间切片的核心思想是:当一群任务在一个通道内执行,如果当前的任务不能在50毫秒内执行完,那么为了不阻塞主线程,这个任务应该让出主线程的控制权,使浏览器可以处理其他任务。让出控制权意味着停止执行当前任务,让浏览器去执行其他任务,随后再回来继续执行没有执行完的任务。

所以时间切片的目的是不阻塞主线程,而实现目的的技术手段是将一个长任务拆分成很多个不超过50ms的小任务分散在宏任务队列中执行。

上图可以看到主线程中有一个长任务,这个任务会阻塞主线程。使用时间切片将它切割成很多个小任务后,如下图所示。

可以看到现在的主线程有很多密密麻麻的小任务,我们将它放大后如下图所示。

可以看到每个小任务中间是有空隙的,代表着任务执行了一小段时间后,将让出主线程的控制权,让浏览器执行其他的任务。

使用时间切片的缺点是,任务运行的总时间变长了,这是因为它每处理完一个小任务后,主线程会空闲出来,并且在下一个小任务开始处理之前有一小段延迟。

但是为了避免卡死浏览器,这种取舍是很有必要的。

0x4:如何实践时间切片

时间切片充分利用了“异步”,在早期,可以使用定时器来实现,我们称之为“手动切片”,例如:

btn.onclick = function () {someThing(); // 执行了50毫秒setTimeout(function () {otherThing(); // 执行了50毫秒});
};

上面代码当按钮被点击时,本应执行100毫秒的任务现在被拆分成了两个50毫秒的任务。

在实际应用中,我们可以进行一些封装,封装后的使用效果类似下面这样:

btn.onclick = timeSlicing([someThing, otherThing], function () {console.log('done~');
});

当然,关于timeSlicing这个函数的API的设计并不是本文的重点,这里想说明的是,在早期可以利用定时器来实现手动方式的“时间切片”;

如果切片的粒度不大,那么手动自己改造函数其实也能接受,但是如果需要切割成粒度非常小的逻辑,那么使用generator函数特性,会更加方便。

ES6带来了迭代器的概念,并提供了生成器Generator函数用来生成迭代器对象,虽然Generator函数最正统的用法是生成迭代器对象,但这不妨我们利用它的特性做一些其他的事情。

Generator函数提供了yield关键字,这个关键字可以让函数暂停执行。然后通过迭代器对象的next方法让函数继续执行

利用这个特性,我们可以设计出更方便使用的时间切片,例如:

btn.onclick = timeSlicing(function* () {someThing(); // 执行了50毫秒yield;otherThing(); // 执行了50毫秒
});

可以看到,我们只需要使用yield这个关键字就可以将本应执行100毫秒的任务拆分成了两个50毫秒的任务。

我们甚至可以将yield关键字放在循环里:

btn.onclick = timeSlicing(function* () {while (true) {someThing(); // 执行了50毫秒yield;}
});

上面代码我们写了一个死循环,但依然不会阻塞主线程,浏览器也不会卡死。

下面我们正式利用Generator开始封装一个时间切片执行器。利用generator的特性把每一次yield都放在requestIdleCallback里执行,直到全部执行完毕,就可以轻松达到时间切片的效果了。

//首先我们封装一个时间切片执行器
function timeSlicing(gen) {if (typeof gen !== "function")throw new Error("TypeError: the param expect a generator function");var g = gen();if (!g || typeof g.next !== "function")return;return function next() {var start = performance.now();var res = null;do {res = g.next();} while (res.done !== true && performance.now() - start < 25);if (res.done)return;window.requestIdleCallback(next);};
}
//然后把长任务变成generator函数,交由时间切片执行器来控制执行
const add = function(i){let item = document.createElement("li");item.innerText = 第${i++}条;listDom.appendChild(item);}
function* gen(){let i=0;while(i<100000){yield add(i);i++}
}
//使用时间切片来插入10W条数据
function bigInsert(){timeSlice(gen)()
}

0x5:时间切片实现斐波那契数列

每学习一门新编程语言,便就会被要求自己重新实现一遍斐波那契数列算法。那时,常用的方法即递归法和递推法。那时只对结果感兴趣,只要结果出来了,其他的仿佛就无所谓了。

在了解了generator生成器的方法后,便开始可以尝试使用generator方法去切片长任务执行。

首先介绍下斐波那契序 0,1,1,2,3,5,8,... 就每一项的值都是前两项相加得到的。

递归方法:

首先,先把之前的递归方法再再再实现一遍。

const fibonacci = (n) => {if(n === 0 || n === 1)return n;return fibonacci(n-1) + fibonacci(n-2);
}// 调用
console.log(fibonacci(40))

递归的思路很简单,即不断调用自身方法,直到n为1或0之后,开始一层层返回数据。

使用递归计算大数字时,性能会特别低,原因有以下2点:

  1. 在递归过程中,每创建一个新函数,解释器都会创建一个新的函数栈帧,并且压在当前函数的栈帧上,这就形成了调用栈。因而,当递归层数过大之后,就可能造成调用栈占用内存过大或者溢出。
  2. 分析可以发现,递归造成了大量的重复计算。

generator生成器:

Generator是ES2015的新特性,得益于该特性,我们可以使用生成器方法,制作一个斐波那契数列生成器。

function *fibonacci(n, current = 0, next = 1) {if (n === 0) {return current;}yield current;yield *fibonacci(n-1, next, current + next);
}// 调用
const [...data] = fibonacci(num)
console.log(data);

测试Demo代码在此

0x6:总结

时间切片不是什么高级的api,而是一种根据浏览器渲染特性衍生出的优化方案,是一种优化思想,把计算量过大,容易阻塞渲染的逻辑切割成一个个小的任务来执行,留给浏览器渲染的时间来达到肉眼可见的流畅,本质上并没有优化什么js的计算性能,所以,有些算法的逻辑该优化还是需要从算法的思想上去优化。

文/Davis

关注得物技术,做最潮技术人!

这篇关于【得物技术】时间切片的实践与应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/594038

相关文章

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

zoj3820(树的直径的应用)

题意:在一颗树上找两个点,使得所有点到选择与其更近的一个点的距离的最大值最小。 思路:如果是选择一个点的话,那么点就是直径的中点。现在考虑两个点的情况,先求树的直径,再把直径最中间的边去掉,再求剩下的两个子树中直径的中点。 代码如下: #include <stdio.h>#include <string.h>#include <algorithm>#include <map>#

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

AI行业应用(不定期更新)

ChatPDF 可以让你上传一个 PDF 文件,然后针对这个 PDF 进行小结和提问。你可以把各种各样你要研究的分析报告交给它,快速获取到想要知道的信息。https://www.chatpdf.com/