Follow the Vue community and reply to " add group "
Join us to learn together and make progress every day
Author: sea wide_sky
https://juejin.cn/post/7137728629986820126
There are 100,000 pieces of data on the page, how long does it take to perform complex operations on it?
The table has 4000 rows, 25 columns, and a total of 100,000 pieces of data
Operations include:总和、算术平均、加权平均、最大、最小、计数、样本标准差、样本方差、中位数、总体标准差、总体方差
The answer is: about 35s
Note: The specific time will vary according to the computer configuration
And
during this time period, the page has been in a state of suspended animation, and there is no response to any operation on the page (exploding in place)
What is suspended animation?
The browser has a GUI rendering thread and a JS engine thread, and these two threads are mutually exclusive.
When js has a large number of calculations, it will cause UI blocking, interface rendering freezes, frame drops, etc., and in severe cases, the page will freeze, commonly known as suspended animation .
fatal bug
Force a test
Miss Test: Your page is dead again! !
Me: I'm not dead yet, I'm in the ICU... It'll be fine in a while. Miss
Test: I've been waiting for a while, but it still doesn't work. It's a fatal bug
. I:...
After dozens of years in the front end, I was brought up with a fatal bug. What's the face!
Performance analysis 假死期间
performance
As shown in the figure below: The total time taken for this calculation is 35.45s
The analysis focuses on the following three aspects:
1. FPS:
FPS: Indicates the number of frames transmitted per second, which is a main performance indicator for analyzing animation. The longer the green length, the better the user experience; on the contrary, the longer the red color, the more serious the lag.
It can be seen from the picture that there is a red line in the FPS that lasts for 35s, indicating that the freeze is serious during this period.
2. Flame graph Main
Main: Indicates the running status of the main thread, including js calculation and execution, css style calculation, Layout layout, etc.
Expand Main, the red inverted triangle is Long Task , the execution time of 50ms is a long task, which will block page rendering
It can be seen from the figure that the execution time of the Long Task in the calculation process is 35.45s, which is the cause of the suspended page.
3. Summary statistics summary panel
Summary: Indicates the time occupation statistical report of each indicator
-
Loading: Loading time -
Scripting: js calculation time -
Rendering: render time -
Painting: painting time -
Other: other time -
Idle: browser idle time
Scripting code executes in 35.9s
What can save you, my page
Summon Web Worker, come out Shenlong
Shenlong, I want to make the calculation of the page faster and not freeze
Web Worker take a look at:
In the new specification of HTML5, Web Worker is implemented to introduce the "multi-threading" technology of js, which allows us to load and run another separate one or more js threads in the js thread of the main page running.
In a word: Web Worker is specialized in processing complex calculations, so that the front end has the computing power of the back end
Using Web Workers in Vue
1. Install worker-loader
npm install worker-loader
2. Write worker.js
onmessage = function (e) {
// onmessage获取传入的初始值
let sum = e.data;
for (let i = 0; i < 200000; i++) {
for (let i = 0; i < 10000; i++) {
sum += Math.random()
}
}
// 将计算的结果传递出去
postMessage(sum);
}
复制代码
3. Introduce worker.js through inline loader
import Worker from "worker-loader!./worker"
4. Final code
<template>
<div>
<button @click="makeWorker">开始线程</button>
<!--在计算时 往input输入值时 没有发生卡顿-->
<p><input type="text"></p>
</div>
</template>
<script>
import Worker from "worker-loader!./worker";
export default {
methods: {
makeWorker() {
// 获取计算开始的时间
let start = performance.now();
// 新建一个线程
let worker = new Worker();
// 线程之间通过postMessage进行通信
worker.postMessage(0);
// 监听message事件
worker.addEventListener("message", (e) => {
// 关闭线程
worker.terminate();
// 获取计算结束的时间
let end = performance.now();
// 得到总的计算时间
let durationTime = end - start;
console.log('计算结果:', e.data);
console.log(`代码执行了 ${durationTime} 毫秒`);
});
}
},
}
</script>
复制代码
During the calculation process, enter the value in the input box, and the page has not been stuck.
Comparative Test
If this code is directly thrown into the main thread during the
calculation process, the page is always in a suspended state, and the input box cannot be entered.
let sum = 0;
for (let i = 0; i < 200000; i++) {
for (let i = 0; i < 10000; i++) {
sum += Math.random()
}
}
复制代码
Foreplay is almost over, let's serve hard dishes
Enable multi-threading, parallel computing
Back to the problem to be solved
When performing multiple operations, open a separate thread for each operation, and close the thread in time after the calculation is completed.
multithreaded code
<template>
<div>
<button @click="makeWorker">开始线程</button>
<!--在计算时 往input输入值时 没有发生卡顿-->
<p><input type="text"></p>
</div>
</template>
<script>
import Worker from "worker-loader!./worker";
export default {
data() {
// 模拟数据
let arr = new Array(100000).fill(1).map(() => Math.random()* 10000);
let weightedList = new Array(100000).fill(1).map(() => Math.random()* 10000);
let calcList = [
{type: 'sum', name: '总和'},
{type: 'average', name: '算术平均'},
{type: 'weightedAverage', name: '加权平均'},
{type: 'max', name: '最大'},
{type: 'middleNum', name: '中位数'},
{type: 'min', name: '最小'},
{type: 'variance', name: '样本方差'},
{type: 'popVariance', name: '总体方差'},
{type: 'stdDeviation', name: '样本标准差'},
{type: 'popStandardDeviation', name: '总体标准差'}
]
return {
workerList: [], // 用来存储所有的线程
calcList, // 计算类型
arr, // 数据
weightedList // 加权因子
}
},
methods: {
makeWorker() {
this.calcList.forEach(item => {
let workerName = `worker${this.workerList.length}`;
let worker = new Worker();
let start = performance.now();
worker.postMessage({arr: this.arr, type: item.type, weightedList: this.weightedList});
worker.addEventListener("message", (e) => {
worker.terminate();
let tastName = '';
this.calcList.forEach(item => {
if(item.type === e.data.type) {
item.value = e.data.value;
tastName = item.name;
}
})
let end = performance.now();
let duration = end - start;
console.log(`当前任务: ${tastName}, 计算用时: ${duration} 毫秒`);
});
this.workerList.push({ [workerName]: worker });
})
},
clearWorker() {
if (this.workerList.length > 0) {
this.workerList.forEach((item, key) => {
item[`worker${key}`].terminate && item[`worker${key}`].terminate(); // 终止所有线程
});
}
}
},
// 页面关闭,如果还没有计算完成,要销毁对应线程
beforeDestroy() {
this.clearWorker();
},
}
</script>
复制代码
worker.js
import { create, all } from 'mathjs'
const config = {
number: 'BigNumber',
precision: 20 // 精度
}
const math = create(all, config);
//加
const numberAdd = (arg1,arg2) => {
return math.number(math.add(math.bignumber(arg1), math.bignumber(arg2)));
}
//减
const numberSub = (arg1,arg2) => {
return math.number(math.subtract(math.bignumber(arg1), math.bignumber(arg2)));
}
//乘
const numberMultiply = (arg1, arg2) => {
return math.number(math.multiply(math.bignumber(arg1), math.bignumber(arg2)));
}
//除
const numberDivide = (arg1, arg2) => {
return math.number(math.divide(math.bignumber(arg1), math.bignumber(arg2)));
}
// 数组总体标准差公式
const popVariance = (arr) => {
return Math.sqrt(popStandardDeviation(arr))
}
// 数组总体方差公式
const popStandardDeviation = (arr) => {
let s,
ave,
sum = 0,
sums= 0,
len = arr.length;
for (let i = 0; i < len; i++) {
sum = numberAdd(Number(arr[i]), sum);
}
ave = numberDivide(sum, len);
for(let i = 0; i < len; i++) {
sums = numberAdd(sums, numberMultiply(numberSub(Number(arr[i]), ave), numberSub(Number(arr[i]), ave)))
}
s = numberDivide(sums,len)
return s;
}
// 数组加权公式
const weightedAverage = (arr1, arr2) => { // arr1: 计算列,arr2: 选择的权重列
let s,
sum = 0, // 分子的值
sums= 0, // 分母的值
len = arr1.length;
for (let i = 0; i < len; i++) {
sum = numberAdd(numberMultiply(Number(arr1[i]), Number(arr2[i])), sum);
sums = numberAdd(Number(arr2[i]), sums);
}
s = numberDivide(sum,sums)
return s;
}
// 数组样本方差公式
const variance = (arr) => {
let s,
ave,
sum = 0,
sums= 0,
len = arr.length;
for (let i = 0; i < len; i++) {
sum = numberAdd(Number(arr[i]), sum);
}
ave = numberDivide(sum, len);
for(let i = 0; i < len; i++) {
sums = numberAdd(sums, numberMultiply(numberSub(Number(arr[i]), ave), numberSub(Number(arr[i]), ave)))
}
s = numberDivide(sums,(len-1))
return s;
}
// 数组中位数
const middleNum = (arr) => {
arr.sort((a,b) => a - b)
if(arr.length%2 === 0){ //判断数字个数是奇数还是偶数
return numberDivide(numberAdd(arr[arr.length/2-1], arr[arr.length/2]),2);//偶数个取中间两个数的平均数
}else{
return arr[(arr.length+1)/2-1];//奇数个取最中间那个数
}
}
// 数组求和
const sum = (arr) => {
let sum = 0, len = arr.length;
for (let i = 0; i < len; i++) {
sum = numberAdd(Number(arr[i]), sum);
}
return sum;
}
// 数组平均值
const average = (arr) => {
return numberDivide(sum(arr), arr.length)
}
// 数组最大值
const max = (arr) => {
let max = arr[0]
for (let i = 0; i < arr.length; i++) {
if(max < arr[i]) {
max = arr[i]
}
}
return max
}
// 数组最小值
const min = (arr) => {
let min = arr[0]
for (let i = 0; i < arr.length; i++) {
if(min > arr[i]) {
min = arr[i]
}
}
return min
}
// 数组有效数据长度
const count = (arr) => {
let remove = ['', ' ', null , undefined, '-']; // 排除无效的数据
return arr.filter(item => !remove.includes(item)).length
}
// 数组样本标准差公式
const stdDeviation = (arr) => {
return Math.sqrt(variance(arr))
}
// 数字三位加逗号,保留两位小数
const formatNumber = (num, pointNum = 2) => {
if ((!num && num !== 0) || num == '-') return '--'
let arr = (typeof num == 'string' ? parseFloat(num) : num).toFixed(pointNum).split('.')
let intNum = arr[0].replace(/\d{1,3}(?=(\d{3})+(.\d*)?$)/g,'$&,')
return arr[1] === undefined ? intNum : `${intNum}.${arr[1]}`
}
onmessage = function (e) {
let {arr, type, weightedList} = e.data
let value = '';
switch (type) {
case 'sum':
value = formatNumber(sum(arr));
break
case 'average':
value = formatNumber(average(arr));
break
case 'weightedAverage':
value = formatNumber(weightedAverage(arr, weightedList));
break
case 'max':
value = formatNumber(max(arr));
break
case 'middleNum':
value = formatNumber(middleNum(arr));
break
case 'min':
value = formatNumber(min(arr));
break
case 'variance':
value = formatNumber(variance(arr));
break
case 'popVariance':
value = formatNumber(popVariance(arr));
break
case 'stdDeviation':
value = formatNumber(stdDeviation(arr));
break
case 'popStandardDeviation':
value = formatNumber(popStandardDeviation(arr));
break
}
// 发送数据事件
postMessage({type, value});
}
复制代码
35s becomes 6s
From the original 35s to the longest 6s, and there is no lag during the calculation process, YYDS
final effect
100,000 data is too low, let’s play with millions of data
// 修改上文的模拟数据
let arr = new Array(1000000).fill(1).map(() => Math.random()* 10000);
let weightedList = new Array(1000000).fill(1).map(() => Math.random()* 10000);
复制代码
The time has obviously come up. It will take more than 50 seconds at most. If you have nothing to play, you will be happy.
Web workers improve Canvas running speed
In addition to simple calculations, web workers
can also draw with off-screen canvas to improve the rendering performance and user experience of the drawing.
Case:
<template>
<div>
<button @click="makeWorker">开始绘图</button>
<canvas id="myCanvas" width="300" height="150"></canvas>
</div>
</template>
<script>
import Worker from "worker-loader!./worker";
export default {
methods: {
makeWorker() {
let worker = new Worker();
let htmlCanvas = document.getElementById("myCanvas");
// 使用canvas的transferControlToOffscreen函数获取一个OffscreenCanvas对象
let offscreen = htmlCanvas.transferControlToOffscreen();
// 注意:第二个参数不能省略
worker.postMessage({canvas: offscreen}, [offscreen]);
}
}
}
</script>
复制代码
worker.js
onmessage = function (e) {
// 使用OffscreenCanvas(离屏Canvas)
let canvas = e.data.canvas;
// 获取绘图上下文
let ctx = canvas.getContext('2d');
// 绘制一个圆弧
ctx.beginPath() // 开启路径
ctx.arc(150, 75, 50, 0, Math.PI*2);
ctx.fillStyle="#1989fa";//设置填充颜色
ctx.fill();//开始填充
ctx.stroke();
}
复制代码
Effect:
Advantages of off-screen canvas
1. For complex canvas drawing, you can avoid blocking the main thread
2. Due to this decoupling, the rendering of OffscreenCanvas is completely separated from the DOM, and the speed of OffscreenCanvas is a little faster than that of ordinary Canvas.
Limitations of Web Workers
1. There is no window global object in the running environment of the Worker thread, nor can the DOM object be accessed
2、Worker中只能获取到部分浏览器提供的 API,如定时器
、navigator
、location
、XMLHttpRequest
等
3、由于可以获取XMLHttpRequest
对象,可以在 Worker 线程中执行ajax
请求
4、每个线程运行在完全独立的环境中,需要通过postMessage
、 message
事件机制来实现的线程之间的通信
计算时长 超过多长时间 适合用Web Worker
原则:
运算时间超过50ms会造成页面卡顿,属于Long task,这种情况就可以考虑使用Web Worker
但还要先考虑通信时长
的问题
假如一个运算执行时长为100ms, 但是通信时长为300ms, 用了Web Worker可能会更慢
通信时长
新建一个web worker时, 浏览器会加载对应的worker.js资源
下图中的Time是这个js资源的总时长: 包括加载时间、执行时间
最终标准:
计算的运算时长 - 通信时长 > 50ms,推荐使用Web Worker
场景补充说明
遇到大数据,第一反应: 为什么不让后端去计算呢?
这里比较特殊,表格4000行,25列
1)用户可以对表格进行灵活操作,比如删除任何行或列,选择或剔除任意行
2)用户可以灵活选择运算的类型,计算一个或多个
即便是让后端计算,需要把大量数据传给后端,计算好再返回,这个时间也不短
还可能出现用户频繁操作,接口数据被覆盖等情况
总结
Web Worker为前端带来了后端的计算能力,扩大了前端的业务边界
可以实现主线程与复杂计运算线程的分离,从而减轻了因大量计算而造成UI阻塞的情况
并且更大程度地利用了终端硬件的性能
参考链接
Multithreading in JavaScript\-- Web Worker [1]
OffscreenCanvas-Off-screen canvas instructions [2]
❤️ After reading two things
If you find this content inspiring, I would like to invite you to do me two small favors:
-
Click " Watching ", so that more people can see this content (if you like it or not, it's all a hooligan -_-)
-
Pay attention to the public account " Vue Community ", and focus on overcoming a difficult front-end interview every week.
You can get 27 selected front-end e-books for free by replying to " e-book " in the background of the official account!