迭代大型数据集的Excel VBA与Javascript性能
Excel VBA vs Javascript Performance for Iterating Large Datasets
我目前正在做一个项目,涉及到将大型数据集过滤到可管理的数据点数量,这些数据点可以绘制在图表上。
我用Javascript编写了以下代码,它遍历一系列数据,并在给定步长处挑选出第一个值,在本例中,起始值= 0,步长= 0.1。这非常有效并且执行得非常快;我没有量化它,但肯定<1秒>10000个数据点。
var data = [ ... ];
var filteredData = [];
var index = 0;
var step = 0.1;
for (var i=0; i < data.length; i++) {
if(data[i] >= index) {
filteredData.push(data[i]);
index+=step;
}
}
Codepen Javascript with Mini Sample Dataset
然而,我们所有的数据都是作为Excel工作簿进来的,所以我使用VBA将代码重写为Excel宏,如下所示,将数据点输出到相邻的列。与JS相比,处理相同数量的数据点需要很长时间,处理10000个数据点大约需要20秒。
Dim dataRange As Range
Set dataRange = Range(Range("A8"), Range("A8").End(xlDown))
Dim index As Double
Dim stepsize As Double
Dim outputRow As Integer
index = 0
step = 0.1
outputRow = 8
For Each cell In dataRange
If cell.Value >= index Then
ActiveSheet.Cells(outputRow, 2).Value = cell.Value
index = index + stepsize
outputRow = outputRow + 1
End If
Next cell
为什么这两种方法之间存在如此巨大的差异?有什么明显的效率低下关于我的VB代码?我希望这个问题不要太模糊!
非常感谢,Adam
看看你的代码的数组实现,它几乎和JS一样快。
对于10,000个数据点,它将花费(至少在我的机器上)几分之一秒。
Sub test()
Dim dataRange As Range
Set dataRange = Range(Range("A8"), Range("A8").End(xlDown))
Dim index As Double
Dim stepsize As Double
Dim outputRow As Long
index = 0
step = 0.1
outputRow = 8
'/ Array implementation in VBA
'/ Its almost at the same speed.
'----------------------------------------------------
Dim lctr As Long
Dim oRow As Long
Dim arrOut()
Dim arr
arr = dataRange
For lctr = LBound(arr) To UBound(arr)
If arr(lctr, 1) >= index Then
index = index + stepsize
oRow = oRow + 1
ReDim Preserve arrOut(1 To 1, 1 To oRow)
arrOut(1, oRow) = arr(lctr, 1)
End If
Next
arrOut = Application.Transpose(arrOut)
ActiveSheet.Cells(8, 2).Resize(UBound(arrOut)) = arrOut
'------------------------------------------------------------
' For Each cell In dataRange
' If cell.Value >= index Then
' ActiveSheet.Cells(outputRow, 2).Value = cell.Value
' index = index + stepsize
' outputRow = outputRow + 1
' End If
'Next cell
End Sub
我从cyobashu中获得了灵感,但通过避免重复调用Redim Preserve
和Transpose
方法来提高性能。
当我对1m行运行cyboashu的代码时,大约需要16秒。当我对1m行运行下面的代码时,大约需要1秒。
我也修复了我认为是一个错字step = 0.1
应该是stepsize = 0.1
Sub test()
Dim dataRange As Range
Set dataRange = Range(Range("A8"), Range("A8").End(xlDown))
Dim index As Double
Dim stepsize As Double
Dim outputRow As Long
index = 0
stepsize = 0.1
outputRow = 8
'/ Array implementation in VBA
'/ Its almost at the same speed.
'----------------------------------------------------
Dim lctr As Long
Dim oRow As Long
Dim arrOut()
Dim arr
arr = dataRange
ReDim arrOut(LBound(arr) To UBound(arr), LBound(arr, 2) To UBound(arr, 2)) As Variant
For lctr = LBound(arr) To UBound(arr)
If arr(lctr, 1) >= index Then
index = index + stepsize
oRow = oRow + 1
arrOut(oRow, 1) = arr(lctr, 1)
End If
Next
ActiveSheet.Cells(8, 2).Resize(oRow) = arrOut
End Sub
很多性能问题来自于不良的访问方法。就VBA和JS的RAW性能而言,如果我们制作相同的2个代码示例,将数据映射到数组中:
Const max As Long = 50000000
Sub test()
Dim i As Long, arr(1 To max) As Long
DEV.Timer_Start
For i = 1 To max
arr(i) = i
Next
DEV.Timer_Stop
End Sub
和
cons max = 50000000
var arr=[]
x=performance.now()
for(var i=0;i<max;i++) arr.push(i)
console.log((performance.now()-x)/1000)
:
VBA Average time: 1.01371899999867 seconds
JS Average time: 1.81799999999930 seconds
所以在原始计算时间方面,JS似乎比VBA慢得多,但这主要是因为这两种操作在内部是完全不同的。
VBA定义了一个200000000字节长的内存块,然后填充每个字节。JavaScript更加动态地执行,并逐渐扩展它的内存占用,如下所示:
Const max As Long = 50000000
Sub test()
Dim i As Long, arr As Collection
Set arr = New Collection
DEV.Timer_Start
For i = 1 To max
Call arr.Add(i)
Next
DEV.Timer_Stop
End Sub
这会降低VBA代码的运行速度。
对于500,000行测试,我得到以下结果:
VBA fixed size long array: 0.00933219999933 seconds
VBA fixed size variant array: 0.01075579999815 seconds
JS dynamic size variant array: 0.03299999999953 seconds
VBA Collection: 0.10702589999709 seconds
VBA dynamic size long array: 0.60271329999886 seconds
VBA stdArray: 7.95831580000231 seconds
VBA dynamic size variant array: 8.36757760000182 seconds
注:starray是一个面向对象的替代JS数组我已经创建。显然,我自己的库的性能有很多改进!
编辑
我现在意识到,stdArray的令人难以置信的缓慢得分的原因是直接与内部使用的变量类型有关。这里我对一个变量数组进行循环,结果循环耗时8.368秒。
Sub test5()
Dim i As Long, arr() As Variant
ReDim arr(1 To 1) As Variant
DEV.Timer_Start
For i = 1 To max
ReDim Preserve arr(1 To UBound(arr) + 1) As Variant
arr(i) = i
Next
DEV.Timer_Stop
End Sub
这需要8.368
秒来运行。这确实说明了在创建数组时选择正确的变量类型是多么重要。在VBA中使用可变数据类型时,一切都非常慢。
编辑:
包含测试用例和性能细节的链接GIST: https://gist.github.com/sancarn/1f92164f1b53fcd940640f680a06b426
- 它是否创建了许多不利于JavaScript性能的变量
- Javascript性能-在dom上迭代并添加侦听器
- 从自执行函数返回函数的Javascript性能命中率
- 循环的 JavaScript 性能问题
- Javascript性能:如何在数组中循环并检查每个值比indexOf、search和match更快
- 多年来浏览器的Javascript性能改进
- JavaScript性能预热
- 在JavaScript性能方面模拟结构处理的C数组
- 持续集成过程中的Javascript性能测试
- 奇怪的 JavaScript 性能取决于变量范围
- 基于 Chrome 的 JavaScript 性能滞后
- Javascript性能 - 如何提高小脚本的性能
- JavaScript 性能 - 除法还是乘法?/与*
- 避免单个变量是否会加快 JavaScript 性能
- 后台的Javascript性能测试
- 如何衡量JavaScript性能(除了页面加载时间)
- 使用新的javascript性能.在iframe上优化API
- JavaScript性能(arr[i]的类型==“未定义”||num<arr[i])
- 处理大型数组时的JavaScript性能
- Javascript性能-使用$.getScript()添加脚本