复制/粘贴特别vs Range.Value = Range.Value

我已经在这个站点(和别处)多次阅读过,如果可能的话,最好避免在VBAmacros中复制/粘贴。 例如,而不是这样做…

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列,10万行)上testing,复制/粘贴版本明显更快(1.9秒vs 2.7秒)。 即使我首先声明tbl.DataBodyRange为Rangevariables,差异依然存在。

我认为这可能是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 

有谁知道为什么复制/粘贴的方法是如此之快? 是否还有其他的理由避免使用复制/粘贴(假设剪贴板将永远不会在macros运行时在Excel以外使用)?

编辑:这是第一组testing结果比较复制/ PasteValues的数组读/写方法由马克杯在接受的答案描述。 我testing了从1000个单元到100万个单元的范围大小,一次增加1000个,并且对每个范围大小进行了10次testing的平均值。 复制粘贴启动较慢,但很快超过了设定值的方法(在图表上很难看到,但是平衡点是~15k个单元格)。

完整的测试结果

我还在范围的下端(范围从100个单元到100000个单元,每次递增100个)进行了10次进一步的testing,以试图确定哪里出现破损点。 这一次,我用Charles Williams的“MicroTimer”代替了默认的定时器,希望对于亚秒级的定时更准确。 我还包括“Set Array”版本和原始的“.Value = .Value”版本(并且记住将计算切换为Manual,不像第一组testing中那样)。 有趣的是,arrays读/写的方法在这个时候显得更糟糕,大约3300个单元的收支平衡点和更差的峰值性能。 arrays读/写和.Value = .Value之间几乎没有区别,尽pipearrays版本稍微差一些。

完整测试2结果

这是我用于上一轮testing的代码:

 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 

大多数(无论如何),VBAmacros不会“使用集合”并迭代一个范围内的单元格。 不是因为这是一个好主意(不是),而是因为很多人根本不知道更好。

使用Range等对象集合时,最快的循环是For Each循环。 所以我把你的testing,重构了一下,添加了迭代解决scheme的testing,然后我添加了一个数组读/写testing,因为这也是复制单元格值的常用方法。

请注意,我将公式编写设置步骤从个别testing中拉出来。

注意:此代码采取控制stream最佳做法,并将其推到地毯下。 不要在实际代码中使用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 TestArrayDump Sheet1.Range(RANGE_ADDRESS) Debug.Print "Array read+write, 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 Private Sub TestArrayDump(ByVal withRange As Range) Dim arr As Variant arr = withRange.Value withRange.Value = arr End Sub 

我不得不把范围的大小减less一个数量级(否则我仍然会盯着我没有回应的Excel屏幕),但是这是输出 – 当然逐个单元迭代的方法慢得多,但是请注意剪贴板数字与直接Value赋值的比较:

 Testing with 150 cells (10 rows) Pasting from clipboard, single operation: 11.71875ms Setting cell values, single operation: 3.90625ms Array read+write, single operation: 7.8125ms 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 Array read+write, 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 Array read+write, single operation: 3.90625ms Pasting from clipboard, iterative: 1691.40625ms Setting cell values, iterative: 136.71875ms 

因此,对于10行/ 150个单元格,将范围复制到数组中或者指定Range.Value没有任何区别:两者都比剪贴板解决scheme快得多。

显然,迭代方法要慢得多,但要注意剪贴板解决scheme比直接分配范围值多less


时间另一次testing运行。

 Testing with 1500 cells (100 rows) Pasting from clipboard, single operation: 11.71875ms Setting cell values, single operation: 7.8125ms Array read+write, single operation: 3.90625ms 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 Array read+write, 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 Array read+write, single operation: 3.90625ms Pasting from clipboard, iterative: 10964.84375ms Setting cell values, iterative: 1062.5ms 

现在不太清楚了,但是倾销arrays似乎仍然是更可靠的更快的解决scheme。


让我们看看1000行给我们:

 Testing with 15000 cells (1000 rows) Pasting from clipboard, single operation: 15.625ms Setting cell values, single operation: 15.625ms Array read+write, single operation: 15.625ms Pasting from clipboard, iterative: 80324.21875ms Setting cell values, iterative: 11859.375ms 

我没有耐心。 评论迭代testing。

 Testing with 15000 cells (1000 rows) Pasting from clipboard, single operation: 19.53125ms Setting cell values, single operation: 15.625ms Array read+write, single operation: 15.625ms Testing with 15000 cells (1000 rows) Pasting from clipboard, single operation: 23.4375ms Setting cell values, single operation: 15.625ms Array read+write, 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 Array read+write, single operation: 152.34375ms Testing with 150000 cells (10000 rows) Pasting from clipboard, single operation: 46.875ms Setting cell values, single operation: 148.4375ms Array read+write, single operation: 148.4375ms Testing with 150000 cells (10000 rows) Pasting from clipboard, single operation: 50.78125ms Setting cell values, single operation: 144.53125ms Array read+write, single operation: 152.34375ms 

而在这里,我们 – 剪贴板显然赢得了!


底线:如果你有100K细胞的工作,剪贴板可能是一个好主意。 如果您有10K个单元格(或更less), Value赋值 数组转储可能是更快的方法。 之间的任何事情可能需要基准testing和testing来找出更快的方法。

TL; DR:没有银弹一刀切的解决scheme。

当你使用相对较less的单元格和/或迭代单个单元格时,您将希望避免复制/粘贴。 对于涉及大量数据的大型批量操作,剪贴板不是一个疯狂的想法。

为了完成:

 Testing with 1500000 cells (100000 rows) Pasting from clipboard, single operation: 324.21875ms Setting cell values, single operation: 1496.09375ms Array read+write, single operation: 1578.125ms Testing with 1500000 cells (100000 rows) Pasting from clipboard, single operation: 324.21875ms Setting cell values, single operation: 1445.3125ms Array read+write, single operation: 1515.625ms Testing with 1500000 cells (100000 rows) Pasting from clipboard, single operation: 367.1875ms Setting cell values, single operation: 1562.5ms Array read+write, single operation: 1574.21875ms 

对于巨大的 YUGE范围,直接设置单元格值似乎始终优于数组转储,但剪贴板performance优于两者,并有相当的余量。

所以:

  • 小于1K的单元格:赋值或数组转储,testing以查明
  • 小于100K的单元格:arrays转储
  • 超过150K的单元格:剪贴板
  • 任何之间的数组转储或剪贴板,testing找出
  • 在任何情况下,更快的方法是一个迭代的解决scheme,几个数量级。