【问题标题】:Increasingly Long Runtime for Macro宏的运行时间越来越长
【发布时间】:2014-08-06 13:49:08
【问题描述】:

我的代码有效,但问题是运行时间越来越长,每次使用宏时完成计算所需的时间都会增加。我已经尝试了对语法的各种变化和修改,但是由于我对 VBA 还很陌生,所以我还没有取得很大的进步。这是我正在运行的代码(注意,它作为子集运行,ScreenUpdate = False):

Public Sub deleteRows()

    Dim lastRow As Long
    Dim rng As Range
    With ActiveSheet
        .AutoFilterMode = False
        lastRow = .Cells(.Rows.Count, 2).End(xlUp).Row
        '~~> Set the range of interest, no need to include the entire data range
            With .Range("B2:F" & lastRow)
                .AutoFilter Field:=2, Criteria1:="=0.000", Operator:=xlFilterValues
                .AutoFilter Field:=5, Criteria1:="=0.000", Operator:=xlFilterValues
            End With
        .Range("B1:F" & lastRow).SpecialCells(xlCellTypeVisible).EntireRow.Delete
        .AutoFilterMode = False
        Rows("1:1").Select
        Selection.Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
    End With
    MsgBox Format(Time - start, "hh:mm:ss")

End Sub

此代码基本上通过删除整行来从数据中删除零值结果。最初,它在大约 12 秒内运行,但很快就变成了 55 秒,这已经发展为越来越长的运行时间,“快速”现在在 5 小步舞范围内。下面是一个电子表格,其中包含记录的运行时间和相应的更改:

Runtime Changes
6:30    None
7:50    None
5:37    Manually stepped through code
7:45    Run with .cells instead of .range("B1:B" & lastRow)
5:21    Run with .Range(B:B)  instead of .range("B1:B" & lastRow)
9:20    Run with application.calculation disabled/enabled, range unchanged
5:35    Run with application.enableEvents disabled/enabled, range unchanged
11:08   Run with application.enableEvents disabled/enabled, Range(B:B)
5:12    None
7:57    Run with Alternative code (old code)
5:45    Range changed to .Range(cells(2,2), Cells(lastRow,2)
10:25   Range changed to .Range(cells(2,2), Cells(lastRow,2), Application.Calculation Disabled/enabled
5:34    Range set to rngB  for .delete portion (range assigned to variable)
9:59    Range set as rng("B1:F" & lastRow)
5:58    Changed system settings for Excel to "High Priority", code reverted to original
9:41    Rerun of old code for comparison
9:26    Reun with change in old code criteria to "0.000"
0:10    Moved SpecialCells……..Delete into 2nd With/End With
5:15    Rerun  SpecialCells……..Delete into 2nd With/End With
11:31   Rerun  SpecialCells……..Delete into 2nd With/End With
11:38   Excel restart; Rerun  SpecialCells……..Delete into 2nd With/End With
5:18    Excel restart; Rerun  SpecialCells……..Delete into 2nd With/End With
6:49    Removed 2nd with 'loop'; all data put into first with statement

我在网上做了一些研究,看起来这可能是 Excel 在处理大型数据集时的一个已知问题,因为我的行数约为 51k,我可以看到可能是这种情况。 “...在早期版本的 Excel 中需要几秒钟才能完成的宏在更高版本的 Excel 中可能需要几分钟才能完成。或者,如果您再次运行宏,宏可能需要两倍的时间才能完成像第一次那样跑。”来源:http://support.microsoft.com/kb/199505

所以我的问题是:有什么方法可以让它运行得更快,就像最初那样?为什么会这样?

【问题讨论】:

  • PS:运行时日志中引用的“旧代码”可以在这里找到:stackoverflow.com/questions/25068737/…
  • 格式重要吗?如果没有,也许尝试使用 ADO 查询出您想要的单元格(字段 2 和 5 0.000)并将记录集粘贴到新工作表(或者甚至删除整个范围并仅粘贴查询的结果)?
  • 我看到了类似的问题,虽然我的第一次运行大约需要 25 秒,而现在大约 90 秒用于后续运行。问题似乎出在.EntireRow.Delete 声明中,这就是一直占用的时间。
  • @JohnBus​​tos 好建议。复制过滤后的范围,然后删除整个范围,然后粘贴。
  • 也是一个同样好的建议@DavidZemens ...停止删除特定行的需要...

标签: excel performance vba optimization time


【解决方案1】:

这是我通过将数据传输到数组然后将数组打印到工作表所做的几次测试的结果。这比任何复制/粘贴以及任何类型的.Delete 方法都高效得多,尤其是在循环中调用时。

这些都在大约一秒内执行,并“删除”了大约 35000 多行。

Start 8/6/2014 1:51:14 PM
Start copy data to array 8/6/2014 1:51:14 PM    lastRow=50000
End copy data to array 8/6/2014 1:51:14 PM for 12270 rows
Start print to sheet 8/6/2014 1:51:14 PM
End print to sheet 8/6/2014 1:51:14 PM
Finished 8/6/2014 1:51:14 PM


Start 8/6/2014 1:51:15 PM
Start copy data to array 8/6/2014 1:51:15 PM    lastRow=50000
End copy data to array 8/6/2014 1:51:15 PM for 12339 rows
Start print to sheet 8/6/2014 1:51:15 PM
End print to sheet 8/6/2014 1:51:15 PM
Finished 8/6/2014 1:51:15 PM


Start 8/6/2014 1:51:16 PM
Start copy data to array 8/6/2014 1:51:16 PM    lastRow=50000
End copy data to array 8/6/2014 1:51:16 PM for 12275 rows
Start print to sheet 8/6/2014 1:51:16 PM
End print to sheet 8/6/2014 1:51:16 PM
Finished 8/6/2014 1:51:16 PM


Start 8/6/2014 1:51:17 PM
Start copy data to array 8/6/2014 1:51:17 PM    lastRow=50000
End copy data to array 8/6/2014 1:51:17 PM for 12178 rows
Start print to sheet 8/6/2014 1:51:17 PM
End print to sheet 8/6/2014 1:51:17 PM
Finished 8/6/2014 1:51:17 PM


Start 8/6/2014 1:51:18 PM
Start copy data to array 8/6/2014 1:51:18 PM    lastRow=50000
End copy data to array 8/6/2014 1:51:18 PM for 12130 rows
Start print to sheet 8/6/2014 1:51:18 PM
End print to sheet 8/6/2014 1:51:18 PM
Finished 8/6/2014 1:51:18 PM

这是我用来测试它的代码:

Sub TimerLoop()
Dim i As Integer
For i = 1 To 5
    deleteRows
Next
End Sub

这里是修改后的函数;请注意,我更改了过滤器参数以确保我将删除足够多的行。运行前改回您自己的标准。

Public Sub deleteRows()
Range("B2:F50000").Formula = "=Round(Rand(),2)"

Dim values As Variant
Dim rng As Range
Dim visible As Range
Dim a As Range, r As Range
Dim nextRow As Long
Dim lastRow As Long
Dim totalRows As Long
Dim i As Long

Application.ScreenUpdating = False
Debug.Print "Start " & Now()

    With ActiveSheet
        .AutoFilterMode = False
        lastRow = .Cells(.Rows.Count, 2).End(xlUp).Row

        'Use a range variable instaead of literal construction:
        Set rng = .Range("B2:F" & lastRow)

            With rng
                .AutoFilter Field:=2, Criteria1:=">0.500", Operator:=xlFilterValues
                .AutoFilter Field:=5, Criteria1:=">0.500", Operator:=xlFilterValues
            End With

            'Assign the values to an array:
            Debug.Print "Start copy data to array " & Now() & vbTab & "lastRow=" & lastRow

            Set visible = rng.SpecialCells(xlCellTypeVisible)

            For Each a In visible.Areas
                For Each r In a.Rows
                totalRows = totalRows + 1
                'values(i) = r.Value
                Next
            Next

            ReDim values(1 To totalRows)

            For Each a In visible.Areas
                For Each r In a.Rows
                    i = i + 1
                    values(i) = r.Value
                Next
            Next


            'Turn off autofilter, clear the cells
            .AutoFilterMode = False
            rng.ClearContents
            Debug.Print "End copy data to array " & Now() & " for " & totalRows & " rows"
            'Put the values back in to the sheet, from the array
            Debug.Print "Start print to sheet " & Now()

            rng.Rows(1).Resize(totalRows).Value = _
                Application.Transpose(Application.Transpose(values))

            Debug.Print "End print to sheet " & Now()

        .AutoFilterMode = False
        Rows("1:1").Select
        Selection.Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
    End With
Debug.Print "Finished " & Now() & vbCrLf & vbCrLf
Application.ScreenUpdating = True
End Sub

【讨论】:

  • 它肯定跑得更快,但我仍然平均每次运行 3:30 分钟。我正在一台带有双四核 i7 和 32 gigs 内存的机器上处理,所以我认为我不受硬件限制......不过我确实必须消除大约 20k 行数据,所以这可以解释时间区别?
  • 运行代码时,第一次运行耗时 7s,然后是 3:36、3:04、4:14,第 5 次运行耗时 5:07。我被难住了。
  • 这很奇怪。我有 I5 处理器,而且很确定我也没有 32GB 内存……也许它在被删除的行数中。格式化重要还是只粘贴 可以吗?
  • 另外,我应该补充一点,当我使用随机数据运行代码时,它会在不到一秒的时间内运行。我认为这表明运行时依赖于必须删除的行。
  • 大卫,您的帮助不只是帮助!非常感谢!我想出了一个让它工作的方法,所以我想我已经准备好了。非常感谢您的帮助!
【解决方案2】:

如果您的电子表格中有公式,我会在开头添加 Application.Calculation = xlCalculationManual,在末尾添加 Application.Calculation = xlCalculationAutomatic,以确保您不会在每次删除行时都重新计算。

【讨论】:

    猜你喜欢
    • 2012-10-17
    • 2018-01-23
    • 1970-01-01
    • 2022-11-12
    • 1970-01-01
    • 2016-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多