我已经在本网站(以及其他地方)多次阅读过最好避免在VBA宏中复制/粘贴的情况。例如,而不是这样做......
For i = 1 To tbl.ListColumns.Count
With tbl.ListColumns(i).DataBodyRange
.FormulaR1C1 = "=2*1"
.Copy
.PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
End With
Next
......据说这样做更好/更快:
For i = 1 To tbl.ListColumns.Count
With tbl.ListColumns(i)
.DataBodyRange.FormulaR1C1 = "=2*1"
.DataBodyRange = .DataBodyRange.Value
End With
Next
但是在大桌子(15列,100k行)上测试它,复制/粘贴版本明显更快(1.9秒对2.7秒)。即使我首先将tbl.DataBodyRange声明为Range变量,差异仍然存在。
我认为这可能是ListObjects的一些奇怪属性,但如果没有它们,差异实际上更大:
'Runs in 1.1 seconds
With Sheet1.Range("A1:O100000")
.FormulaR1C1 = "=2*1"
.Copy
.PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
End With
'Runs in 2.1 seconds
With Sheet1.Range("A1:O100000")
.FormulaR1C1 = "=2*1"
.Value = .Value
End With
有谁知道为什么复制/粘贴方法要快得多?是否有其他理由避免使用复制/粘贴(假设在宏运行时剪贴板永远不会在Excel之外使用)?
编辑:这是第一组测试结果,它将Copy / PasteValues与Mat的Mug在接受的答案中描述的数组读/写方法进行比较。我测试了1000个细胞到100万个细胞的范围大小,每次增加1000个,并且每个范围大小平均进行10次测试。复制粘贴开始变慢,但很快超过设定值方法(在图表上很难看到,但收支平衡点是~15k单元格)。
我还在该范围的下端进行了10次进一步的测试(范围从100个细胞到100000个细胞,每次递增100个)以试图确定发生收支平衡点的位置。这次我使用Charles Williams' "MicroTimer"而不是默认计时器,希望它对于亚秒计时更准确。我还包括了" Set Array"版本和原作" .Value = .Value"版本(并记住将计算切换到手动,与第一组测试不同)。有趣的是,此次阵列读/写方法的表现明显更差,大约3300个单元的均衡点和更差的峰值性能。数组读/写和.Value = .Value之间几乎没有区别,尽管阵列版本表现稍差。
这是我用于最后一轮测试的代码:
Sub speedTest()
Dim copyPasteRNG(1 To 10, 1 To 1000)
Dim setValueRNG(1 To 10, 1 To 1000)
Dim setValueArrRNG(1 To 10, 1 To 1000)
Dim i As Long
Dim j As Long
Dim numRows As Long
Dim rng As Range
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.DisplayStatusBar = False
For i = 1 To 10
numRows = 100
For j = 1 To 1000
Set rng = Sheet3.Range("A1:A" & numRows)
setValueRNG(i, j) = getTime(False, rng, False)
setValueArrRNG(i, j) = getTime(False, rng, True)
numRows = numRows + 100
Next
Next
For i = 1 To 10
numRows = 100
For j = 1 To 1000
Set rng = Sheet3.Range("A1:A" & numRows)
copyPasteRNG(i, j) = getTime(True, rng)
numRows = numRows + 100
Next
Next
Sheet4.Range("A1:J1000").Value2 = Application.Transpose(copyPasteRNG)
Sheet5.Range("A1:J1000").Value2 = Application.Transpose(setValueRNG)
Application.DisplayStatusBar = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Function getTime(copyPaste As Boolean, rng As Range, Optional arrB As Boolean) As Double
Dim startTime As Double
Dim endTime As Double
startTime = MicroTimer
With rng
.FormulaR1C1 = "=1"
If copyPaste = True Then
.Copy
.PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
ElseIf arrB = True Then
Dim arr As Variant
arr = .Value2
.Value2 = arr
Else
.Value2 = .Value2
End If
End With
endTime = MicroTimer - startTime
getTime = endTime
End Function
这是我使用的MicroTimer版本(在单独的模块中):
Private Declare PtrSafe Function getFrequency Lib "kernel32" Alias "QueryPerformanceFrequency" (cyFrequency As Currency) As Long
Private Declare PtrSafe Function getTickCount Lib "kernel32" Alias "QueryPerformanceCounter" (cyTickCount As Currency) As Long
Private Const sCPURegKey = "HARDWARE\DESCRIPTION\System\CentralProcessor\0"
Private Const HKEY_LOCAL_MACHINE As Long = &H80000002
Private Declare PtrSafe Function RegCloseKey Lib "advapi32.dll" (ByVal hKey As Long) As Long
Private Declare PtrSafe Function RegOpenKey Lib "advapi32.dll" Alias "RegOpenKeyA" (ByVal hKey As Long, ByVal lpSubKey As String, phkResult As Long) As Long
Private Declare PtrSafe Function RegQueryValueEx Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal lpReserved As Long, lpType As Long, lpData As Any, lpcbData As Long) As Long
Function MicroTimer() As Double
Dim cyTicks1 As Currency
Static cyFrequency As Currency
'
MicroTimer = 0
If cyFrequency = 0 Then getFrequency cyFrequency
getTickCount cyTicks1
If cyFrequency Then MicroTimer = cyTicks1 / cyFrequency
End Function
答案 0 :(得分:6)
大多数(很多,无论如何)VBA宏不“使用集合”并迭代一个范围内的单元格。不是因为这是一个好主意(不是),而是因为很多人根本不知道更好。
使用对象集合(如Range
)时,最快的循环是For Each
循环。所以我接受了你的测试,稍微重构了一下,添加了迭代解决方案的测试,然后我添加了一个数组读/写测试,因为这也是复制单元格值的常用,好方法。
请注意,我从单独的测试中拉出了公式编写设置步骤。
注意:此代码采用控制流程最佳实践并将其推到地毯下。 不要在实际代码中使用GoSub
/ Return
。
Sub Test()
Const TEST_ROWCOUNT As Long = 10
Const RANGE_ADDRESS As String = "A1:O" & TEST_ROWCOUNT
Const RANGE_FORMULA As String = "=2*1"
Dim startTime As Double
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Debug.Print "Testing with " & Sheet1.Range(RANGE_ADDRESS).Count & " cells (" & TEST_ROWCOUNT & " rows)"
GoSub InitTimer
TestPasteFromClipboard Sheet1.Range(RANGE_ADDRESS)
Debug.Print "Pasting from clipboard, single operation:",
GoSub ReportTime
GoSub InitTimer
TestSetRangeValue Sheet1.Range(RANGE_ADDRESS)
Debug.Print "Setting cell values, single operation:",
GoSub ReportTime
GoSub InitTimer
TestIteratePaste Sheet1.Range(RANGE_ADDRESS)
Debug.Print "Pasting from clipboard, iterative:",
GoSub ReportTime
GoSub InitTimer
TestIterateSetValue Sheet1.Range(RANGE_ADDRESS)
Debug.Print "Setting cell values, iterative:",
GoSub ReportTime
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Exit Sub
InitTimer:
Sheet1.Range(RANGE_ADDRESS).Formula = RANGE_FORMULA
startTime = Timer
Return
ReportTime:
Debug.Print (Timer - startTime) * 1000 & "ms"
Return
End Sub
Private Sub TestPasteFromClipboard(ByVal withRange As Range)
With withRange
.Copy
.PasteSpecial Paste:=xlPasteValues
End With
Application.CutCopyMode = False
End Sub
Private Sub TestSetRangeValue(ByVal withRange As Range)
withRange.Value = withRange.Value
End Sub
Private Sub TestIteratePaste(ByVal withRange As Range)
Dim cell As Range
For Each cell In withRange.Cells
cell.Copy
cell.PasteSpecial Paste:=xlPasteValues
Next
Application.CutCopyMode = False
End Sub
Private Sub TestIterateSetValue(ByVal withRange As Range)
Dim cell As Range
For Each cell In withRange.Cells
cell.Value = cell.Value
Next
Application.CutCopyMode = False
End Sub
我不得不将范围大小缩小一个数量级(否则我仍然会盯着我的无响应的Excel屏幕),但这是输出 - 当然逐个细胞的迭代方法很多较慢,但请注意剪贴板数字与直接Value
赋值的比较:
Testing with 150 cells (10 rows)
Pasting from clipboard, single operation: 11.71875ms
Setting cell values, single operation: 3.90625ms
Pasting from clipboard, iterative: 1773.4375ms
Setting cell values, iterative: 105.46875ms
Testing with 150 cells (10 rows)
Pasting from clipboard, single operation: 11.71875ms
Setting cell values, single operation: 3.90625ms
Pasting from clipboard, iterative: 1718.75ms
Setting cell values, iterative: 109.375ms
Testing with 150 cells (10 rows)
Pasting from clipboard, single operation: 15.625ms
Setting cell values, single operation: 3.90625ms
Pasting from clipboard, iterative: 1691.40625ms
Setting cell values, iterative: 136.71875ms
因此,对于10行/ 150个单元格,将范围复制到数组/分配Range.Value
比剪贴板解决方案快得多。
显然迭代方法要慢得多,但与直接指定范围值相比,注意剪贴板解决方案的速度有多慢!
另一次测试运行的时间。
Testing with 1500 cells (100 rows)
Pasting from clipboard, single operation: 11.71875ms
Setting cell values, single operation: 7.8125ms
Pasting from clipboard, iterative: 10480.46875ms
Setting cell values, iterative: 1125ms
Testing with 1500 cells (100 rows)
Pasting from clipboard, single operation: 19.53125ms
Setting cell values, single operation: 3.90625ms
Pasting from clipboard, iterative: 10859.375ms
Setting cell values, iterative: 2390.625ms
Testing with 1500 cells (100 rows)
Pasting from clipboard, single operation: 15.625ms
Setting cell values, single operation: 3.90625ms
Pasting from clipboard, iterative: 10964.84375ms
Setting cell values, iterative: 1062.5ms
现在不太明确,但倾销阵列似乎仍然是更可靠更快的解决方案。
让我们看看1000行给了我们:
Testing with 15000 cells (1000 rows)
Pasting from clipboard, single operation: 15.625ms
Setting cell values, single operation: 15.625ms
Pasting from clipboard, iterative: 80324.21875ms
Setting cell values, iterative: 11859.375ms
我没有耐心。评论迭代测试。
Testing with 15000 cells (1000 rows)
Pasting from clipboard, single operation: 19.53125ms
Setting cell values, single operation: 15.625ms
Testing with 15000 cells (1000 rows)
Pasting from clipboard, single operation: 23.4375ms
Setting cell values, single operation: 15.625ms
非常一致;再次,剪贴板丢失。但是10K行呢?
Testing with 150000 cells (10000 rows)
Pasting from clipboard, single operation: 46.875ms
Setting cell values, single operation: 144.53125ms
Testing with 150000 cells (10000 rows)
Pasting from clipboard, single operation: 46.875ms
Setting cell values, single operation: 148.4375ms
Testing with 150000 cells (10000 rows)
Pasting from clipboard, single operation: 50.78125ms
Setting cell values, single operation: 144.53125ms
我们在这里 - 剪贴板现在明显胜出!
底线:如果您要使用100K单元格,剪贴板可能是个好主意。如果你有10K单元可以使用(或更少), 数组转储可能是更快的方法。中间的任何内容都可能需要进行基准测试和测试,以找出更快的方法。Value
赋值
TL; DR:没有银弹一刀切的解决方案。
当您使用相对较少数量的单元格时,和/或如果您正在迭代单个单元格时,您将希望避免复制/粘贴。对于涉及很多数据的大型批量操作,剪贴板不是一个疯狂的想法。
为了完成:
Testing with 1500000 cells (100000 rows)
Pasting from clipboard, single operation: 324.21875ms
Setting cell values, single operation: 1496.09375ms
Testing with 1500000 cells (100000 rows)
Pasting from clipboard, single operation: 324.21875ms
Setting cell values, single operation: 1445.3125ms
Testing with 1500000 cells (100000 rows)
Pasting from clipboard, single operation: 367.1875ms
Setting cell values, single operation: 1562.5ms
对于巨大的 YUGE范围,直接设置单元格值似乎始终优于阵列转储,但剪贴板优于两者,并且相当大。
所以: