-
Notifications
You must be signed in to change notification settings - Fork 315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
时间切片(Time Slicing) #38
Comments
有意思 |
还可以这样用呀? |
可不可以理解为 |
@GitHdu 不可以~ 不一样哒~😁 |
是不是可以理解为,如果一个任务是主线程的,但是它的执行时间超过了50ms, 那就把它丢到异步去,让出主线程。 |
@Kuangchao-hzz 也不是~~ |
博文哥 这个测试的工具叫啥名字啊 |
@calvinchan22 额?什么工具👀 |
|
@vibing 保证每个任务最多执行25ms,每个同步执行的任务执行时间要控制在50ms内,再多就会阻塞主线程导致明显的卡顿。设置50ms我觉得还是不保险,就设置了25,属于经验值吧~ |
demo test 秒 这就是我和大佬💻配置的区别 枯了 逃( |
50 / 2 👍 |
这个跟react的切片有什么不一样的地方? |
这个时间切片,是协程的概念么?js中可以通过generator来创建协程 |
难道只有我的浏览器被卡死了么 😂 |
@zhizhiyaya 看起来是的 😂 |
时间切片(Time Slicing)
上周我在FDConf的分享《让你的网页更丝滑》中提到了“时间切片”,由于时间关系当时并没有对时间切片展开更细致的讨论。所以回来后就想着补一篇文章针对“时间切片”展开详细的讨论。
从用户的输入,再到显示器在视觉上给用户的输出,这一过程如果超过100ms,那么用户会察觉到网页的卡顿,所以为了解决这个问题,每个任务不能超过50ms,W3C性能工作组在LongTask规范中也将超过50ms的任务定义为长任务。
所以为了避免长任务,一种方案是使用Web Worker,将长任务放在Worker线程中执行,缺点是无法访问DOM,而另一种方案是使用时间切片。
什么是时间切片
时间切片的核心思想是:如果任务不能在50毫秒内执行完,那么为了不阻塞主线程,这个任务应该让出主线程的控制权,使浏览器可以处理其他任务。让出控制权意味着停止执行当前任务,让浏览器去执行其他任务,随后再回来继续执行没有执行完的任务。
所以时间切片的目的是不阻塞主线程,而实现目的的技术手段是将一个长任务拆分成很多个不超过50ms的小任务分散在宏任务队列中执行。
上图可以看到主线程中有一个长任务,这个任务会阻塞主线程。使用时间切片将它切割成很多个小任务后,如下图所示。
可以看到现在的主线程有很多密密麻麻的小任务,我们将它放大后如下图所示。
可以看到每个小任务中间是有空隙的,代表着任务执行了一小段时间后,将让出主线程的控制权,让浏览器执行其他的任务。
如何使用时间切片
时间切片是一种概念,也可以理解为一种技术方案,它不是某个API的名字,也不是某个工具的名字。
事实上,时间切片充分利用了“异步”,在早期,可以使用定时器来实现,例如:
上面代码当按钮被点击时,本应执行100毫秒的任务现在被拆分成了两个50毫秒的任务。
在实际应用中,我们可以进行一些封装,封装后的使用效果类似下面这样:
当然,关于
ts
这个函数的API的设计并不是本文的重点,这里想说明的是,在早期可以利用定时器来实现“时间切片”。ES6带来了迭代器的概念,并提供了生成器Generator函数用来生成迭代器对象,虽然Generator函数最正统的用法是生成迭代器对象,但这不妨我们利用它的特性做一些其他的事情。
Generator函数提供了
yield
关键字,这个关键字可以让函数暂停执行。然后通过迭代器对象的next
方法让函数继续执行。利用这个特性,我们可以设计出更方便使用的时间切片,例如:
可以看到,我们只需要使用
yield
这个关键字就可以将本应执行100毫秒的任务拆分成了两个50毫秒的任务。我们甚至可以将yield关键字放在循环里:
上面代码我们写了一个死循环,但依然不会阻塞主线程,浏览器也不会卡死。
基于生成器的ts实现原理
通过前面的例子,我们会发现基于Generator的时间切片非常好用,但其实ts函数的实现原理非常简单,一个最简单的ts函数只需要九行代码。
代码虽然全部只有9行,关键代码只有3、4行,但这几行代码充分利用了事件循环机制以及Generator函数的特性。
上面代码核心思想是:通过
yield
关键字可以将任务暂停执行,从而让出主线程的控制权;通过定时器可以将“未完成的任务”重新放在任务队列中继续执行。避免把任务分解的过于零碎
使用
yield
来切割任务非常方便,但如果切割的粒度特别细,反而效率不高。假设我们的任务执行100ms
,最好的方式是切割成两个执行50ms
的任务,而不是切割成100个执行1ms
的任务。假设被切割的任务之间的间隔为4ms
,那么切割成100个执行1ms
的任务的总执行时间为:如果切割成两个执行时间为
50ms
的任务,那么总执行时间为:可以看到,在不影响用户体验的情况下,下面的总执行时间要比前面的少了4.6倍。
保证切割的任务刚好接近
50ms
,可以在用户使用yield
时自行评估,也可以在ts
函数中根据任务的执行时间判断是否应该一次性执行多个任务。我们将
ts
函数稍微改进一下:现在我们测试下:
这段代码在之前的版本中,在我的电脑上可以打印出 215 次
11
,在后面的版本中可以打印出 6300 次11
,说明在总时间相同的情况下,可以执行更多的任务。再看另一个例子:
在我的电脑上,这段代码在之前的版本中,被切割成一万个小任务,总执行时间为
46
秒,在之后的版本中,被切割成 52 个小任务,总执行时间为1.5
秒。总结
我将时间切片的代码放在了我的Github上,感兴趣的可以参观下:https://github.com/berwin/time-slicing
The text was updated successfully, but these errors were encountered: