【发布时间】:2017-12-14 15:07:47
【问题描述】:
我在这个网站(和其他地方)上多次阅读到,如果可能,最好在 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's 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
【问题讨论】:
-
作为一个出于理智目的几乎总是坚持使用
.Copy [Destination]的人,我很高兴看到它也可能更快。 -
数组读/写和 .Value = .Value 之间几乎没有区别 - 再次阅读这个帖子(刚刚得到一个随机的赞成票),让我印象深刻的一件事是该数组读/写和 .value = .value 是完全相同的东西 - 唯一的区别是额外的“从范围中拉出二维数组”步骤。