页面中有十万条数据,对其进行复杂运算,需要多久呢?
表格4000行,25列,总十万条数据
运算包括:总和、算术平均、加权平均、最大、最小、计数、样本标准差、样本方差、中位数、总体标准差、总体方差
答案是: 35s 左右
注:具体时间根据电脑配置会有所不同
并且 这个时间段内,页面一直处于假死状态,对页面做任何操作都没有反应(原地爆炸)
boom0.png
什么是假死?
浏览器有GUI渲染线程与JS引擎线程,这两个线程是互斥的关系。 当js有大量计算时,会造成 UI 阻塞,出现界面渲染卡顿、掉帧等情况,严重时会出现页面卡死的情况,俗称假死
强行送测吧
测试小姐姐:你的页面又死了!! 我:还没有死,在ICU…… ,过一会就好了 测试小姐姐:已经等了好一会了,还不行啊,是个致命bug 我:……
绝望.jpg
闯荡前端数十载,竟被提了个致命bug,颜面何在!
假死期间
的性能表现如下图所示: 此次计算总用时为35.45s
重点从以下三个方面分析:
1、FPS: FPS: 表示每秒传输帧数,是分析动画的一个主要性能指标,绿色的长度越长,用户体验越好;反之红色越长,说明卡顿严重
从图中看到FPS中有一条持续了35s的红线,说明这期间卡顿严重
2、火焰图Main Main: 表示主线程运行状况,包括js的计算与执行、css样式计算、Layout布局等等。
展开Main,红色倒三角的为Long Task,执行时长50ms就属于长任务,会阻塞页面渲染
从图中看到计算过程的Long Task执行时间为35.45s, 是造成页面假死的原因
3、Summary 统计汇总面板 Summary: 表示各指标时间占用统计报表
Scripting代码执行为35.9s
performance8.png
拿什么拯救你,我的页面
R-C (1).gif
神龙,我想让页面的计算变快,并且不卡顿
Web Worker了解一下:
在HTML5的新规范中,实现了 Web Worker 来引入 js 的 “多线程” 技术, 可以让我们可以在页面主运行的 js 线程中加载运行另外单独的一个或者多个 js 线程。
一句话: Web Worker专门处理复杂计算的,从此让前端拥有后端的计算能力
1、安装worker-loader
npm install worker-loader
2、编写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、通过行内loader 引入 worker.js import Worker from "worker-loader!./worker"
4、最终代码
<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>
复制代码
计算过程中,在input框输入值,页面一直未发生卡顿
total.png
如果直接把这段代码直接丢到主线程中 计算过程中,页面一直处于假死状态,input框无法输入
let sum = 0;
for (let i = 0; i < 200000; i++) {
for (let i = 0; i < 10000; i++) {
sum += Math.random()
}
}
复制代码
开启多线程,并行计算
回到要解决的问题
执行多种运算时,给每种运算开启单独的线程,线程计算完成后要及时关闭
多线程代码
<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变成了最长6s,并且计算过程中全程无卡顿,YYDS
time1.png
src=http___img.soogif.com_n7sySW0OULhVlH5j7OrXHpbqEiM9hDsr.gif&refer=http___img.soogif.gif
最终的效果
table.gif
// 修改上文的模拟数据
let arr = new Array(1000000).fill(1).map(() => Math.random()* 10000);
let weightedList = new Array(1000000).fill(1).map(() => Math.random()* 10000);
复制代码
时间明显上来了,最长要50多s了,没事玩一玩,开心就好
time3.png
web worker除了单纯进行计算外 还可以结合离屏canvas进行绘图,提升绘图的渲染性能和使用体验
案例:
<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();
}
复制代码
效果:
cricle.gif
离屏canvas的优势
1、对于复杂的canvas绘图,可以避免阻塞主线程
2、由于这种解耦,OffscreenCanvas的渲染与DOM完全分离了开来,并且比普通Canvas速度提升了一些
1、在 Worker 线程的运行环境中没有 window 全局对象,也无法访问 DOM 对象
2、Worker中只能获取到部分浏览器提供的 API,如定时器
、navigator
、location
、XMLHttpRequest
等
3、由于可以获取XMLHttpRequest
对象,可以在 Worker 线程中执行ajax
请求
4、每个线程运行在完全独立的环境中,需要通过postMessage
、 message
事件机制来实现的线程之间的通信
原则:
运算时间超过50ms会造成页面卡顿,属于Long task,这种情况就可以考虑使用Web Worker
但还要先考虑通信时长
的问题
假如一个运算执行时长为100ms, 但是通信时长为300ms, 用了Web Worker可能会更慢
face.jpg
新建一个web worker时, 浏览器会加载对应的worker.js资源
下图中的Time是这个js资源的总时长: 包括加载时间、执行时间
load.png
最终标准:
计算的运算时长 - 通信时长 > 50ms,推荐使用Web Worker
遇到大数据,第一反应: 为什么不让后端去计算呢?
这里比较特殊,表格4000行,25列 1)用户可以对表格进行灵活操作,比如删除任何行或列,选择或剔除任意行 2)用户可以灵活选择运算的类型,计算一个或多个
即便是让后端计算,需要把大量数据传给后端,计算好再返回,这个时间也不短 还可能出现用户频繁操作,接口数据被覆盖等情况
Web Worker为前端带来了后端的计算能力,扩大了前端的业务边界
可以实现主线程与复杂计运算线程的分离,从而减轻了因大量计算而造成UI阻塞的情况
并且更大程度地利用了终端硬件的性能
R-C (3).gif
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/vr4jNqSWOFZaY2a5f9XKwQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。