就内存消耗/执行时间而言,将项目添加到组中的更昂贵的方法是什么
Redim Preserve myArray(1 To Ubound(myArray) + 1)
myArray(Ubound(myArray)) = myVal
或者
myCollection.Add myVal
最快的一个取决于myVal
,还是随着群组的大小而变化?还有更快的方法吗?
我在类的声明部分声明了私有的数组/集合,如果这有所不同,但是我想知道幕后发生了什么,哪种方法通常更快(不是在可读性或可维护性方面,只是执行时间)
好的,运行了一些测试,将1个实例添加到组和集合中,我的结果是:
使用此代码循环结果约为5秒:
Sub testtime()
Dim sttime As Double
Dim endtime As Double
Dim i As Long
Dim u As Long
i = 0
ReDim a(1 To 1) 'or Set c = New Collection
sttime = Timer
endtime = Timer + 5
Do Until Timer > endtime
u = UBound(a) + 1
ReDim Preserve a(1 To u)
a(u) = 1
'or c.Add 1
i = i + 1
Loop
endtime = Timer
Debug.Print (endtime - sttime) / i; "s", i; "iterations", Round(endtime - sttime, 3); "(ish) s"
End Sub
所以看起来像是添加相对较大的组的项目;添加到集合更快,但我想知道为什么?
答案 0 :(得分:7)
ReDim Preserve myArray(1 To UBound(myArray) + 1)
这本质上是低效的,而且是不公平的比较。您在内部复制整个阵列,每次添加项目时 。我希望Collection
比这更有效率。如果没有,请使用.NET的ArrayList
,这在.NET中已弃用,因为v2.0引入了泛型和List<T>
,但在VBA中可用 - 并且很有用 - (.NET泛型不能在VBA中使用) )。
ArrayList
每次添加项目时都不会调整其内部_items
数组的大小!请注意评论:
// Adds the given object to the end of this list. The size of the list is // increased by one. If required, the capacity of the list is doubled // before adding the new element. // public virtual int Add(Object value) { Contract.Ensures(Contract.Result<int>() >= 0); if (_size == _items.Length) EnsureCapacity(_size + 1); _items[_size] = value; _version++; return _size++; } ... // Ensures that the capacity of this list is at least the given minimum // value. If the currect capacity of the list is less than min, the // capacity is increased to twice the current capacity or to min, // whichever is larger. private void EnsureCapacity(int min) { if (_items.Length < min) { int newCapacity = _items.Length == 0? _defaultCapacity: _items.Length * 2; // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. // Note that this check works even when _items.Length overflowed thanks to the (uint) cast if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; if (newCapacity < min) newCapacity = min; Capacity = newCapacity; } }
<子> https://referencesource.microsoft.com/#mscorlib/system/collections/arraylist.cs 子>
我不知道VBA.Collection
的内部结构,但如果我不得不猜测,我会说它可能有类似的机制,避免每次添加项目时重新标注内部数组的尺寸。但这一切都没有实际意义,因为除了微软之外没有人知道VBA.Collection
是如何实现的。
我们可以做什么,运行基准并进行比较 - 让我们为一个数组,一个集合和一个ArrayList
添加一百万个值:
Public Sub TestReDimArray()
Dim sut() As Variant
ReDim sut(0 To 0)
Dim i As Long
Dim t As Single
t = Timer
Do While UBound(sut) < 1000000
ReDim Preserve sut(0 To i)
sut(i) = i
i = i + 1
Loop
Debug.Print "ReDimArray added 1M items in " & Timer - t & " seconds."
End Sub
Public Sub TestCollection()
Dim sut As VBA.Collection
Set sut = New VBA.Collection
Dim i As Long
Dim t As Single
t = Timer
Do While sut.Count < 1000000
sut.Add i
i = i + 1
Loop
Debug.Print "Collection added 1M items in " & Timer - t & " seconds."
End Sub
Public Sub TestArrayList()
Dim sut As Object
Set sut = CreateObject("System.Collections.ArrayList")
Dim i As Long
Dim t As Single
t = Timer
Do While sut.Count < 1000000
sut.Add i
i = i + 1
Loop
Debug.Print "ArrayList added 1M items in " & Timer - t & " seconds."
End Sub
这是输出:
ReDimArray added 1M items in 14.90234 seconds.
Collection added 1M items in 0.1875 seconds.
ArrayList added 1M items in 15.64453 seconds.
请注意,引用32位mscorlib.tlb
和早期绑定ArrayList
并没有多大区别。此外还有托管/ COM互操作开销,而VBA不支持构造函数,因此ArrayList
初始化的容量为4
,每次达到容量时都会加倍,即当我们插入第一百万项时,我们'我们将内部阵列重新调整了19次,最终内部容量为1,048,576项。
那么Collection
如何赢得那么多呢?
因为数组被滥用:调整大小不是数组最好的,并且在每次插入之前调整大小都不太可能。
事先知道元素数量时使用数组:
Public Sub TestPopulateArray()
Dim sut(0 To 999999) As Variant
Dim i As Long
Dim t As Single
t = Timer
Do While i < 1000000
sut(i) = i
i = i + 1
Loop
Debug.Print "PopulateArray added 1M items in " & Timer - t & " seconds."
End Sub
输出:
PopulateArray added 1M items in 0.0234375 seconds.
这比向VBA.Collection
添加相同数量的项目快大约10倍 - 使用良好的数组非常快。
尽可能将阵列大小调整到最小 - 避免。如果您不知道最终要使用的项目数,请使用Collection
。如果您这样做,请使用明确大小的Array
。