haizlin/fe-interview

[js] 第330天 js循环的数据量很大(例如100W+)时会出现什么情况?如何进行性能优化?

Opened this issue · 8 comments

第330天 js循环的数据量很大(例如100W+)时会出现什么情况?如何进行性能优化?

我也要出题

现在js代码的编译和执行都是混合了JIT的解释器和编译器两种模式,起初在执行一段代码的时候都是边解释边执行,这种模式在执行普通的代码时候效率更高,因为不需要进行预编译就可以直接运行代码。

但是当遇到大量的循环的时候,这种模式效率就会变得低下,因为每次循环的结果几乎都是一定的,但是解释器会不停地把时间浪费在类型检查和行为决策上。

当一行代码重复了很多次,并且每次行为都是一致的时候,JS引擎就会单独把这段代码丢进编译器(基线编译器)进行编译,编译过的代码效率会更高。

而当这段代码重复了太多次(~>10000次)后,JS引擎会再次将这段代码进行优化编译(优化编译器),从而绕过所有的类型检查和行为决策,直接假设性的做出类型判断然后执行,这种行为非常高效,但也很危险,所以JS会在执行前验证自己的假设,如果假设不合理,则将这段代码丢弃而回到基线编译器或者解释器执行,这反而会浪费大量时间。


所以在进行大量循环的时候,为了保证优化编译器能正常启动,必须保证每次循环的类型一致,不能前10000条数据的num是数字,结果第10001条数据的num是字符串,这在TypeScript里可以很好地保证这一点。

曾经也有人利用优化编译器的这一特性,利用BUG绕过了类型检查,从而让JS将object当成number输出,导致对象内存地址暴露甚至可以随意篡改,有兴趣的可以看看:WebKit-RegEx-Exploit

会发生什么:
循环执行时间可能过长,在循环过程中有可能阻塞(block)主线程,而当主线程被阻塞时,UI界面上用户的交互操作也就没响应了。导致用户体验的急剧恶化。

如何进行性能优化,想到两种方式:

  1. 使用chunks方式,将这种大任务切分为细粒度的小任务,保证每次循环占用CPU的时间不超过100ms。

  2. 可以将这种大数据量的计算任务放到web worker中。然后通过 postMessage 来传递计算结果。

选用合适的数据类型,比如纯数字数组或Uint16Array什么的。
使用 web worker 启用多线程来处理。
减少计算,比如读取入参相同的缓存结果。
若可异步,就拆成多份异步完成。
能循环就不要递归。

js本身不擅长计算,它的任务是处理用户交互。大数据量的计算最好交后台或数据库处理

1.webworker
2.webAssembly
3.耗时任务切割成多个细小任务,然后再浏览器空闲时 执行

能否以for循环为例讲讲什么是保证循环类型一致,执行循环的时候里面还会有if这种判断,肯定不能保障每次的行为是一致的啊

现在js代码的编译和执行都是混合了JIT的解释器和编译器两种模式,起初在执行一段代码的时候都是边解释边执行,这种模式在执行普通的代码时候效率更高,因为不需要进行预编译就可以直接运行代码。

但是当遇到大量的循环的时候,这种模式效率就会变得低下,因为每次循环的结果几乎都是一定的,但是解释器会不停地把时间浪费在类型检查和行为决策上。

当一行代码重复了很多次,并且每次行为都是一致的时候,JS引擎就会单独把这段代码丢进编译器(基线编译器)进行编译,编译过的代码效率会更高。

而当这段代码重复了太多次(~>10000次)后,JS引擎会再次将这段代码进行优化编译(优化编译器),从而绕过所有的类型检查和行为决策,直接假设性的做出类型判断然后执行,这种行为非常高效,但也很危险,所以JS会在执行前验证自己的假设,如果假设不合理,则将这段代码丢弃而回到基线编译器或者解释器执行,这反而会浪费大量时间。

所以在进行大量循环的时候,为了保证优化编译器能正常启动,必须保证每次循环的类型一致,不能前10000条数据的num是数字,结果第10001条数据的num是字符串,这在TypeScript里可以很好地保证这一点。

曾经也有人利用优化编译器的这一特性,利用BUG绕过了类型检查,从而让JS将object当成number输出,导致对象内存地址暴露甚至可以随意篡改,有兴趣的可以看看:WebKit-RegEx-Exploit

能否以for循环为例讲讲什么是保证循环类型一致,执行循环的时候里面还会有if这种判断,肯定不能保障每次的行为是一致的啊,十分感谢

会阻塞主线程,导致页面看起来卡顿。
如何解决:1 webworker多线程 2 切割多个小块异步执行