Javascript 改进大型对象阵列上的循环
我有一个包含7MB 11000个对象的大型JSON文件。每个对象包含多个如下所示的项目:Javascript 改进大型对象阵列上的循环,javascript,json,performance,Javascript,Json,Performance,我有一个包含7MB 11000个对象的大型JSON文件。每个对象包含多个如下所示的项目: [ { ... "startingTime":"Date1", "endingTime":"Date2" ... } ] 在我的HTML中,我有一个包含每日小时数:分钟数的滑块 当用户移动滑块时,我应该通过过滤文件中的数据来更新DOM,以便找到: StartingTime您可以返回原始结构,并在客户端预先计算相同的帮助结构。 为了进一步优化它,您可以通
[
{ ...
"startingTime":"Date1",
"endingTime":"Date2"
...
}
]
在我的HTML中,我有一个包含每日小时数:分钟数的滑块
当用户移动滑块时,我应该通过过滤文件中的数据来更新DOM,以便找到:
StartingTime您可以返回原始结构,并在客户端预先计算相同的帮助结构。
为了进一步优化它,您可以通过预测用户的操作来延迟更新缓存结构,如果缓存尚未填充,则在填充之前阻止ui。您可以返回原始结构,并在客户端预先计算相同的帮助结构。 为了进一步优化它,您可以通过预测用户的操作来延迟更新缓存结构,如果缓存尚未填充,则在填充之前阻止ui 目标是在地图上显示列车运行 这就是我们应该开始的: 乘火车把时间分组。这可以在On中完成,并且不会增加数据集大小,因此也可以在后端完成,请参阅下面的注意事项 那为什么有用呢?这大大减少了我们必须过滤的数据集大小。11000起事件可能只涉及几百列火车 然后,首先生成以下内容: 列车id地图-当前显示的列车 按发车时间排序的列车数组 按到达时间排序的列车数组 在当前滑块位置离开的最后一列列车的位置 最后一列到达当前滑块位置的位置 现在,每当滑块移动时,您所要做的就是: 在出发点数组中移动出发点位置,对于您跨过的每一列火车,将火车添加到地图和DOM中 在到达数组中移动到达位置,对于您跨过的每一列火车,从地图和DOM中删除火车 如果滑块向后移动,则程序正好相反 然后,对于当前显示的每列列车,如有必要,调整位置 这将要快得多,我可以想象,对于一个典型的滑动步,~20列火车移动,~1列火车出发,~1列火车到达,所以你需要做22次更新,而不是11000次 虽然我写了很多优化技巧,但这些技巧还是有用的,如果我写的其他技巧与优化无关的话 您用于在后端分组的代码实际上没有优化,因为它会将数据数组过滤120次。下面是如何在数据集上迭代一次,然后对每个条目遍历它所属的所有分钟并将其添加到其中的方法:
const toMinutes = date => {
const [hours, mins] = date.split(":");
return (+hours * 60 + +mins);
};
const fromMinutes = minutes => {
const mins = minutes % 60;
const hours = (minutes - mins) / 60;
return hours + ":" + mins;
};
const START = "tp_org_r", END = "tp_des_r";
const groupByDate = { /* "01:20": [...] */ };
for(const elm of data) {
const start = toMinutes(elm[START]);
const end = toMinutes(elm[END]);
for(let minutes = start; minutes <= end; minutes++) {
const id = fromMinutes(minutes);
if(!groupByDate[id]) groupByDate[id] = [];
groupByDate[id].push(elm);
}
}
但这种方法有一个很大的缺陷:它大量复制项目。我永远不会在后端执行此操作,因为这将增加发送到前端的文件大小,因此您将失去通过增加将数据发送到前端所需的时间而获得的轻微性能优势。此外,似乎没有办法在后端缓存它,因此对于每个客户端,您都必须重做计算。换句话说:你在浪费你的计算时间。你向服务器付费,客户端是免费的,你不会从中获得任何好处。如果你1减少发送给客户端的数据量,从而减少页面加载时间和/或2计算一次数据,然后将其提供给数百个客户端,那么在服务器上进行计算是有意义的
你可以在前端这样做,但我不认为构建这个可查找表是值得的,因为过滤日期实际上相当容易
相反,将所有元素发送到前端,然后在那里进行过滤。如果数据集是有序的,那么过滤就很简单,甚至不必迭代所有11000个元素:
1按开始时间对数组进行排序
2发现第一个位置的起始时间小于滑块时间减去最大持续时间,我假设这只是几分钟。如果endtime大于滑块时间加上最大持续时间,则对最后一个位置执行相同操作。通过这一点,我们消除了11000中的许多,然后可以很容易地筛选出结果范围
好处可能还不清楚,但如果滑块移动,我们所要做的就是移动开始和结束位置。换句话说,我们不必再次查看所有11000个元素,我们只需将范围稍微向右/向左移动,然后过滤这个小范围
但在我的第一种方法中,滑块每改变一次,但如果用户快速滑动滑块,UI就不流畅了
这很容易解决。如果用户滑得很快,他不想也不需要看到他滑过的事件。请执行以下操作:
1当用户开始滑动时,隐藏当前元素并显示某种加载指示器
2当用户停止滑动时,请稍等片刻,然后
生成过滤结果
通过这种方式,页面只需重新提交两次,这将大大减少计算量,并且UI更加流畅,因为在没有用户交互的情况下进行更新,因此即使计算量很大,用户也不会注意到,因为没有延迟
目标是在地图上显示列车运行
这就是我们应该开始的:
乘火车把时间分组。这可以在On中完成,并且不会增加数据集大小,因此也可以在后端完成,请参阅下面的注意事项
那为什么有用呢?这大大减少了我们必须过滤的数据集大小。11000起事件可能只涉及几百列火车
然后,首先生成以下内容:
列车id地图-当前显示的列车
按发车时间排序的列车数组
按到达时间排序的列车数组
在当前滑块位置离开的最后一列列车的位置
最后一列到达当前滑块位置的位置
现在,每当滑块移动时,您所要做的就是:
在出发点数组中移动出发点位置,对于您跨过的每一列火车,将火车添加到地图和DOM中
在到达数组中移动到达位置,对于您跨过的每一列火车,从地图和DOM中删除火车
如果滑块向后移动,则程序正好相反
然后,对于当前显示的每列列车,如有必要,调整位置
这将要快得多,我可以想象,对于一个典型的滑动步,~20列火车移动,~1列火车出发,~1列火车到达,所以你需要做22次更新,而不是11000次
虽然我写了很多优化技巧,但这些技巧还是有用的,如果我写的其他技巧与优化无关的话
您用于在后端分组的代码实际上没有优化,因为它会将数据数组过滤120次。下面是如何在数据集上迭代一次,然后对每个条目遍历它所属的所有分钟并将其添加到其中的方法:
const toMinutes = date => {
const [hours, mins] = date.split(":");
return (+hours * 60 + +mins);
};
const fromMinutes = minutes => {
const mins = minutes % 60;
const hours = (minutes - mins) / 60;
return hours + ":" + mins;
};
const START = "tp_org_r", END = "tp_des_r";
const groupByDate = { /* "01:20": [...] */ };
for(const elm of data) {
const start = toMinutes(elm[START]);
const end = toMinutes(elm[END]);
for(let minutes = start; minutes <= end; minutes++) {
const id = fromMinutes(minutes);
if(!groupByDate[id]) groupByDate[id] = [];
groupByDate[id].push(elm);
}
}
但这种方法有一个很大的缺陷:它大量复制项目。我永远不会在后端执行此操作,因为这将增加发送到前端的文件大小,因此您将失去通过增加将数据发送到前端所需的时间而获得的轻微性能优势。此外,似乎没有办法在后端缓存它,因此对于每个客户端,您都必须重做计算。换句话说:你在浪费你的计算时间。你向服务器付费,客户端是免费的,你不会从中获得任何好处。如果你1减少发送给客户端的数据量,从而减少页面加载时间和/或2计算一次数据,然后将其提供给数百个客户端,那么在服务器上进行计算是有意义的
你可以在前端这样做,但我不认为构建这个可查找表是值得的,因为过滤日期实际上相当容易
相反,将所有元素发送到前端,然后在那里进行过滤。如果数据集是有序的,那么过滤就很简单,甚至不必迭代所有11000个元素:
1按开始时间对数组进行排序
2发现第一个位置的起始时间小于滑块时间减去最大持续时间,我假设这只是几分钟。如果endtime大于滑块时间加上最大持续时间,则对最后一个位置执行相同操作。通过这一点,我们消除了11000中的许多,然后可以很容易地筛选出结果范围
好处可能还不清楚,但如果滑块移动,我们所要做的就是移动开始和结束位置。换句话说,我们不必再次查看所有11000个元素,我们只需将范围稍微向右/向左移动,然后过滤这个小范围
但在我的第一种方法中,滑块每改变一次,但如果用户快速滑动滑块,UI就不流畅了
这很容易解决。如果用户滑得很快,他不想也不需要看到他滑过的事件。请执行以下操作:
1当用户开始滑动时,隐藏当前元素并显示某种加载指示器
2当用户停止滑动时,等待一小段时间,然后生成过滤结果
通过这种方式,页面只需重新提交两次,这将大大减少计算量,并且UI更加流畅,因为在没有用户交互的情况下进行更新,因此即使计算量很大,用户也不会注意到,因为没有延迟
为了获得更好的性能,您应该知道最大的问题是渲染,而不是渲染
过滤
您的问题来自于使用日期对象和比较日期。为了获得更好的性能,您可以首先将数据从日期类型转换为以毫秒为单位的数字类型,然后将数字与
我们彼此都很高兴。我更改了您的代码,您可以看到示例
您应该知道,javascript中的循环性能各不相同:
我比较了使用for、for of、while、forEach和reduce的随机10k项的总和。运行10000次测试返回以下结果:
For Loop, average loop time: ~10 microseconds
For-Of, average loop time: ~110 microseconds
ForEach, average loop time: ~77 microseconds
While, average loop time: ~11 microseconds
Reduce, average loop time: ~113 microseconds
如果需要,您可以以正确的方式使用Object.keys和Object.values以及Array.map和Array.filter
设formatTrips=函数数据{
var arr=[],i,j;
fori=0;i{
让schedulesFiltered=[];
schedulesFiltered=dataWithTimeStamp.filterm=>elm.tp\u org\u r=time;
//console.logschedulesFiltered
返回计划过滤
};
var end=new Date.getTime;
var时间=结束-开始;
日志“执行时间:”+时间;
}
//创建示例数据
常量sampleData=[];
const date2019ToMiliseconds=新日期2019-01-01.valueOf;
2019年后的常数毫秒=新的日期.valueOf-日期2019到毫秒;
forlet i=0;我
为了获得更好的性能,您应该知道最大的问题是渲染,而不是渲染
过滤
您的问题来自于使用日期对象和比较日期。为了获得更好的性能,您可以首先将数据从日期类型转换为以毫秒为单位的数字类型,然后相互比较数字。我更改了您的代码,您可以看到示例
您应该知道,javascript中的循环性能各不相同:
我比较了使用for、for of、while、forEach和reduce的随机10k项的总和。运行10000次测试返回以下结果:
For Loop, average loop time: ~10 microseconds
For-Of, average loop time: ~110 microseconds
ForEach, average loop time: ~77 microseconds
While, average loop time: ~11 microseconds
Reduce, average loop time: ~113 microseconds
如果需要,您可以以正确的方式使用Object.keys和Object.values以及Array.map和Array.filter
设formatTrips=函数数据{
var arr=[],i,j;
fori=0;i{
让schedulesFiltered=[];
schedulesFiltered=dataWithTimeStamp.filterm=>elm.tp\u org\u r=time;
//console.logschedulesFiltered
返回计划过滤
};
var end=new Date.getTime;
var时间=结束-开始;
日志“执行时间:”+时间;
}
//创建示例数据
常量sampleData=[];
const date2019ToMiliseconds=新日期2019-01-01.valueOf;
2019年后的常数毫秒=新的日期.valueOf-日期2019到毫秒;
forlet i=0;你有没有量过表演?1过滤需要多长时间?2重新招标需要多长时间?3.25秒太长了,你做错了什么。你能展示一下代码吗?每次用户更改滑块时,你都会对JSON数据发出HTTP请求吗?还是在页面加载时作为JS变量执行一次?添加过滤代码,不清楚我是否以某种方式排序?你知道你过滤数据120次…2小时是120次是的,但我应该执行24小时,所以在我的第二种方法中为1440次。。但在我的第一种方法中,滑块每改变一次,但如果用户快速滑动滑块,UI就会变得不流畅。您衡量过性能了吗?1过滤需要多长时间?2重新招标需要多长时间?3.25秒太长了,你做错了什么。你能展示一下代码吗?每次用户更改滑块时,你都会对JSON数据发出HTTP请求吗?还是在页面加载时作为JS变量执行一次?添加过滤代码,不清楚我是否以某种方式排序?你知道你过滤数据120次…2小时是120次是的,但我应该执行24小时,所以在我的第二种方法中为1440次。。但在我的第一种方法中,滑块每改变一次,但如果用户快速滑动滑块,UI就不会流动,所以前端的过滤速度比后端快?实际上,数据集并没有被过滤。顺便说一句,我没有使用secondsMaybe,如果不保存完整数据,而是保存对它的引用?e、 g.您将所有数据保存在地图中,并将其密钥存储在您的groupByDate对象中。由于我只使用了小时:分钟,您可以编辑您的代码并添加一些注释,因为我不完全了解您在做什么。Thanks@infodev不,假设您在chrome中访问页面,并且您的后端在NodeJS上运行,那么无论您是在前端还是后端计算,它所用的时间大致与在引擎盖下运行相同的JS引擎所用的时间相同。但加载时间也是一个性能特性。如果优化后的数据结构是14mb,那么客户端将花费两倍的时间来加载/解析数据。我不明白你为什么要对它进行排序,但是如果你想的话,把对象变成一个数组,然后对它进行排序。所以在前端进行过滤比后端更快?实际上,数据集并没有被过滤。顺便说一句,我没有使用secondsMaybe,如果不保存完整数据,而是保存对它的引用?e、 g.您将所有数据保存在地图中,并将它们的密钥存储在groupByDate对象中。由于我只使用了小时:分钟,您可以编辑您的代码并添加一些注释吗?因为我不完全了解您的意思
你在做什么。Thanks@infodev不,假设您在chrome中访问页面,并且您的后端在NodeJS上运行,那么无论您是在前端还是后端计算,它所用的时间大致与在引擎盖下运行相同的JS引擎所用的时间相同。但加载时间也是一个性能特性。如果优化后的数据结构是14mb,那么客户端将花费两倍的时间来加载/解析数据。我不明白你为什么要对它排序,但是如果你想,把对象变成一个数组,然后排序。谢谢,你认为前端的预计算比后端快吗?谢谢,你认为前端的预计算比后端快吗?