与访问用户定义类型的字段相比,VBA类模块属性是否较慢?
我一直在尝试开发一个带有类模块的宏,但与UDT相比,get/let似乎花费了很长时间。我真的很想知道为什么,有人能解释一下吗?我只发现讨论函数/子执行的讨论,似乎也一样快 当前的问题是设置一个属性,这个类大约需要3000毫秒(200万个let),使用UDT进行同样的操作需要120毫秒 我试图决定是否应该建议宏开发人员在需要获取或设置大量属性时避免使用类模块。我应该只使用这些数据,但也许你有不同的见解 我想了解为什么这是如此缓慢。也许我只是做错了什么 示例代码:与访问用户定义类型的字段相比,VBA类模块属性是否较慢?,vba,excel,Vba,Excel,我一直在尝试开发一个带有类模块的宏,但与UDT相比,get/let似乎花费了很长时间。我真的很想知道为什么,有人能解释一下吗?我只发现讨论函数/子执行的讨论,似乎也一样快 当前的问题是设置一个属性,这个类大约需要3000毫秒(200万个let),使用UDT进行同样的操作需要120毫秒 我试图决定是否应该建议宏开发人员在需要获取或设置大量属性时避免使用类模块。我应该只使用这些数据,但也许你有不同的见解 我想了解为什么这是如此缓慢。也许我只是做错了什么 示例代码: Public Type Parti
Public Type Participant
Name As String
Gender As Integer
End Type
Public Declare Function GetTickCount Lib "kernel32.dll" () As Long
Sub TimeUDT()
Dim i As Long
Dim startMs As Long
startMs = GetTickCount
Dim participants(1 To 1000000) As Participant
For i = 1 To 1000000
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
Debug.Print GetTickCount - startMs
End Sub
Sub TimeCls()
Dim i As Long
Dim startMs As Long
Dim participants(1 To 1000000) As New clsParticipant
startMs = GetTickCount
For i = 1 To 1000000
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
Debug.Print GetTickCount - startMs
End Sub
以及类模块(命名为CLSPArticPant):
首先,我强烈建议使用高分辨率计时器,这样您就不必测试那么多的迭代。请参阅使用
QueryPerformanceCounter
这是我机器上的基线,10K迭代,高精度计时器
Sub TimeUDT()
Dim i As Long
Dim timer As New CTimer
timer.StartCounter
Dim participants(1 To 10000) As Participant
For i = 1 To 10000
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
Debug.Print "Elapsed time: " & timer.TimeElapsed & " ms"
End Sub
Elapsed time: 1.14359022404999 ms
不管你信不信由你,你实际上是在你的循环中进行对象创建的。在启动计时器之前,在循环中显式创建它们,并查看差异:
以前
Sub TimeCls()
Dim i As Long
Dim timer As New CTimer
Dim participants(1 To 10000) As New clsParticipant
timer.StartCounter
For i = 1 To 10000
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
Debug.Print "Elapsed time: " & timer.TimeElapsed & " ms"
End Sub
Elapsed time: 24.9600996727434 ms
之后
这仅比基线慢4倍(对象创建命中后,现在已从测量中排除)。如果您进一步声明您的iGender
和sName
为公共,并直接对它们进行变异,那么性能将更接近基线,因此剩下的大部分性能影响来自Let
间接定向
Sub TimeCls()
Dim i As Long
Dim timer As New CTimer
Dim participants(1 To 10000) As clsParticipant
For i = 1 To 10000
Set participants(i) = New clsParticipant
Next i
timer.StartCounter
For i = 1 To 10000
'participants(i).Name = "TestName"
'participants(i).Gender = 1
participants(i).sName = "TestName"
participants(i).iGender = 1
Next
Debug.Print "Elapsed time: " & timer.TimeElapsed & " ms"
End Sub
Elapsed time: 1.71887815565976 ms
<>一个Webb在他的答案中正确地强调了在测试中,你没有考虑创建对象所需的时间,而是忘记增加对象的破坏。 下面代码中的时间考虑了创建和销毁时间,并表明没有什么是免费的,也就是说,不管你怎么做,最终总时间大致相同 我还添加了最后3个函数,它们以三种不同的方式显式销毁对象,而不是将其留给垃圾收集器,时间保持不变 我添加了这三个测试,因为我希望看到巨大的变化。我记得几年前我做过一个测试,垃圾收集器需要花费10到100倍的时间,只是改变了销毁的顺序。我无法在这里重现这个问题,可能是因为对象没有那么多,或者是因为对象比较简单。但是我添加代码只是为了显示另一个测试,如果您注意到销毁所需的时间突然增加,那么您应该执行该测试 这是我的密码:
Sub Test()
Dim T0 As Single
T0 = timer
TimeCls_a
Debug.Print , timer - T0
T0 = timer
TimeCls_b
Debug.Print , timer - T0
T0 = timer
TimeCls_c
Debug.Print , timer - T0
T0 = timer
TimeCls_c_up
Debug.Print , timer - T0
T0 = timer
TimeCls_c_dn
Debug.Print , timer - T0
T0 = timer
TimeCls_c_all
Debug.Print , timer - T0
End Sub
Sub TimeCls_a()
Dim i As Long
Dim T0 As Single
Dim participants(1 To NCYCLES) As New clsParticipant
T0 = timer
For i = 1 To NCYCLES
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
Debug.Print "TimeCls_a:", timer - T0
End Sub
Sub TimeCls_b()
Dim i As Long
Dim T0 As Single
Dim participants(1 To NCYCLES) As clsParticipant
For i = 1 To NCYCLES
Set participants(i) = New clsParticipant
Next i
T0 = timer
For i = 1 To NCYCLES
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
Debug.Print "TimeCls_b:", timer - T0
End Sub
Sub TimeCls_c()
Dim i As Long
Dim T0 As Single
Dim participants(1 To NCYCLES) As clsParticipant
For i = 1 To NCYCLES
Set participants(i) = New clsParticipant
Next i
T0 = timer
For i = 1 To NCYCLES
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
Debug.Print "TimeCls_c:", timer - T0
End Sub
Sub TimeCls_c_up()
Dim i As Long
Dim T0 As Single
Dim participants(1 To NCYCLES) As clsParticipant
For i = 1 To NCYCLES
Set participants(i) = New clsParticipant
Next i
T0 = timer
For i = 1 To NCYCLES
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
For i = 1 To NCYCLES
Set participants(i) = Nothing
Next i
Debug.Print "TimeCls_c_up:", timer - T0
End Sub
Sub TimeCls_c_dn()
Dim i As Long
Dim T0 As Single
Dim participants(1 To NCYCLES) As clsParticipant
For i = 1 To NCYCLES
Set participants(i) = New clsParticipant
Next i
T0 = timer
For i = 1 To NCYCLES
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
For i = NCYCLES To 1 Step -1
Set participants(i) = Nothing
Next i
Debug.Print "TimeCls_c_dn:", timer - T0
End Sub
Sub TimeCls_c_al()
Dim i As Long
Dim T0 As Single
Dim participants() As clsParticipant
ReDim participants(1 To NCYCLES)
For i = 1 To NCYCLES
Set participants(i) = New clsParticipant
Next i
T0 = timer
For i = 1 To NCYCLES
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
ReDim participants(1 To NCYCLES)
Debug.Print "TimeCls_c_al:", timer - T0
End Sub
我的猜测是,您正在失去一个可伸缩性问题,而不一定执行缓慢。如果你在少量的迭代中进行测试,我猜它们每次执行的时间只有300毫秒。如果你的设计真的需要RAM中的200万个类实例,我想你最好看看另一个设计。是的,你在类对象方面遇到了同样的问题,我几年前不得不处理这些问题。我被“起草”去分析一个巨大的access数据库,这个数据库已经在全球范围内投入生产,而且运行起来就像一条狗。我在每个函数和子例程中都放置了计时器,发现对类的引用是疯狂的。我切换到了一些全局变量和宾果!我从来没有费心去理解为什么那些特定的类速度很慢,但这与初始化有关(不,它们是非常简单的类)。@rheitzman:不幸的是,由于这是一个正在重新建模的大型项目,所以目前还不能改变设计。例如,为10.000个对象运行它确实需要0毫秒,但真正的问题是需要设置很多属性,只有少量(打开的工作簿数量)实例。它用于保存计算每个打开工作簿时使用的数据。因此,UDT不是一个选项,因为数据集太大,类模块太慢。@WayneG.Dunn:如果我理解正确,您在类模块之外使用全局变量?我们需要将这些作为实例,否则就必须在另一个模块中保留一个单独的设置数组。至于初始化:我在初始化类实例数组后启动计时器,所以我想这不会是本例中的问题。我真的不理解3“缺少足够信息”在这个问题上的投票结果。它写得很清楚,并包含一个演示问题的示例。它的质量远远高于excel、vba标签的标准价格。非常好的细分+伟大的回应!非常感谢,我意识到我可能看到了由于异步创建实例而导致的初始化,但是我直到现在才有时间测试,我看到您已经为我完成了这项工作。感谢您花时间解释这一点。我认为类字段在VBA中必须是私有的。显然我还有一些东西要学。感谢您花时间测试并向我解释这一点!我们试图隔离字段访问(设置)的成本,因此故意排除对象创建和销毁时间。但是,如果对象的寿命较短,则这些是有效的注意事项。我对你所说的销毁顺序会影响性能并不感到惊讶。VBA使用引用计数,因此可能必须根据关系和顺序传播更多的引用计数递减。
Sub TimeCls()
Dim i As Long
Dim timer As New CTimer
Dim participants(1 To 10000) As clsParticipant
For i = 1 To 10000
Set participants(i) = New clsParticipant
Next i
timer.StartCounter
For i = 1 To 10000
'participants(i).Name = "TestName"
'participants(i).Gender = 1
participants(i).sName = "TestName"
participants(i).iGender = 1
Next
Debug.Print "Elapsed time: " & timer.TimeElapsed & " ms"
End Sub
Elapsed time: 1.71887815565976 ms
Sub Test()
Dim T0 As Single
T0 = timer
TimeCls_a
Debug.Print , timer - T0
T0 = timer
TimeCls_b
Debug.Print , timer - T0
T0 = timer
TimeCls_c
Debug.Print , timer - T0
T0 = timer
TimeCls_c_up
Debug.Print , timer - T0
T0 = timer
TimeCls_c_dn
Debug.Print , timer - T0
T0 = timer
TimeCls_c_all
Debug.Print , timer - T0
End Sub
Sub TimeCls_a()
Dim i As Long
Dim T0 As Single
Dim participants(1 To NCYCLES) As New clsParticipant
T0 = timer
For i = 1 To NCYCLES
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
Debug.Print "TimeCls_a:", timer - T0
End Sub
Sub TimeCls_b()
Dim i As Long
Dim T0 As Single
Dim participants(1 To NCYCLES) As clsParticipant
For i = 1 To NCYCLES
Set participants(i) = New clsParticipant
Next i
T0 = timer
For i = 1 To NCYCLES
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
Debug.Print "TimeCls_b:", timer - T0
End Sub
Sub TimeCls_c()
Dim i As Long
Dim T0 As Single
Dim participants(1 To NCYCLES) As clsParticipant
For i = 1 To NCYCLES
Set participants(i) = New clsParticipant
Next i
T0 = timer
For i = 1 To NCYCLES
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
Debug.Print "TimeCls_c:", timer - T0
End Sub
Sub TimeCls_c_up()
Dim i As Long
Dim T0 As Single
Dim participants(1 To NCYCLES) As clsParticipant
For i = 1 To NCYCLES
Set participants(i) = New clsParticipant
Next i
T0 = timer
For i = 1 To NCYCLES
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
For i = 1 To NCYCLES
Set participants(i) = Nothing
Next i
Debug.Print "TimeCls_c_up:", timer - T0
End Sub
Sub TimeCls_c_dn()
Dim i As Long
Dim T0 As Single
Dim participants(1 To NCYCLES) As clsParticipant
For i = 1 To NCYCLES
Set participants(i) = New clsParticipant
Next i
T0 = timer
For i = 1 To NCYCLES
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
For i = NCYCLES To 1 Step -1
Set participants(i) = Nothing
Next i
Debug.Print "TimeCls_c_dn:", timer - T0
End Sub
Sub TimeCls_c_al()
Dim i As Long
Dim T0 As Single
Dim participants() As clsParticipant
ReDim participants(1 To NCYCLES)
For i = 1 To NCYCLES
Set participants(i) = New clsParticipant
Next i
T0 = timer
For i = 1 To NCYCLES
participants(i).Name = "TestName"
participants(i).Gender = 1
Next
ReDim participants(1 To NCYCLES)
Debug.Print "TimeCls_c_al:", timer - T0
End Sub