提高FOR循环的性能 [英] Improving the performance of FOR loop
问题描述
我有通常建议的方法提高性能,如屏幕更新设置为FALSE等。
我想优化两个 FOR NEXT
循环
Dim RESULT As String
iPRE = ActiveWorkbook.Worksheets(PRE)。范围(A1 ,Worksheets(PRE)。Range(A1)。End(xlDown))。Rows.Count
'MsgBox iPRE
iPOST = ActiveWorkbook.Worksheets(POST)。Range A1,Worksheets(POST)。Range(A1)。End(xlDown))。Rows.Count
'MsgBox iPOST
如果iPRE& iPOST然后
MsgBoxPRE和POST表中的行数不一样,宏退出
退出子
Else
iRows = iPRE
结束如果
'优化性能
Application.ScreenUpdating = False
EventState = Application.EnableEvents
Application.EnableEvents = False
CalcState = Application.Calculation
Application.Calculation = xlCalculationManual
PageBreakState = ActiveSheet.DisplayPageBreaks
ActiveSheet.DisplayPageBreaks = False
对于iCntr = iRows到2步骤-1
对于y = 1到20
如果工作表(PRE)。单元格(iCntr,y)工作表(POST)。单元格(iCntr,y)然后
RESULT =DeleteN
退出
Else
RESULT =DeleteY
End If
下一个y
如果RESULT =DeleteY然后
工作表(PRE)。Rows(iCntr).Delete
工作表(POST)。Rows iCntr).Delete
End If
Next iCntr
'还原optmizing行
ActiveSheet.DisplayPageBreaks = PageBreakState
Application.Calculation = CalcState
Application.EnableEvents = EventState
Application.ScreenUpdating = True
End Sub
任何对工作表单元格的引用都很慢。当循环中这样做时,这会大大增加。最好的速度增长将来自限制这些工作表引用。
一个好的方法是复制Variant数组中的数据,并循环遍历这些,构建一个新的Variant Array,要保存的数据。然后将新阵列一次性放在旧位置上。
使用20万行,20列,50%文本,50%数字的测试数据集,删除17万行:此代码在我的硬件上运行约30秒
Sub Mine2()
Dim T1 As Long, T2 As Long,T3 As Long
Dim ResDelete As Boolean
Dim iPRE As Long,iPOST As Long
Dim EventState As Boolean,CalcState As XlCalculation,PageBreakState As Boolean
Dim iCntr As Long,y As Long,iRows As Long
Dim rPre As Range,rPost As Range
Dim PreDat As Variant,PostDat As Variant,PreDelDat As Variant,PostDelDat As Variant
Dim n As Long
Dim wsPre As Worksheet,wsPost as Worksheet
设置wsPre = ActiveWorkbook.Worksheets(PRE)
使用wsPre
设置rPre = .Range(.Cells(1,.Columns.Count).End(xlToLeft),.Cells(.Rows.Count,1).End(xlUp))
PreDat = rPre.Value
iPRE = UBoun d(PreDat,1)
'MsgBox iPRE
结束
设置wsPost = ActiveWorkbook.Worksheets(POST)
使用wsPost
设置rPost = .Range(.Cells(1,.Columns.Count).End(xlToLeft),.Cells(.Rows.Count,1).End(xlUp))
PostDat = rPost.Value
iPOST = UBound(PostDat,1)
'MsgBox iPOST
结束
如果iPRE<> iPOST然后
MsgBoxPRE和POST表中的行数不一样,宏退出
Exit Sub
End If
iRows = iPRE
ReDim PreDelDat(1 To UBound(PreDat,1),1 To UBound(PreDat,2))
ReDim PostDelDat(1 To UBound(PostDat,1),1 To UBound(PostDat, 2))
n = 1
错误GoTo EH:
'优化性能
Application.ScreenUpdating = False
EventState = Application.EnableEvents
Application.EnableEvents = False
CalcState = Application.Calculation
Application.Calculation = xlCalculationManual
PageBreakState = ActiveSheet.DisplayPageBreaks
ActiveSheet.DisplayPageBreaks = False
T1 = GetTickCount
对于y = 1到UBound(PreDat,2)
PreDelDat(1,y)= PreDat(1,y)
PostDelDat(1,y)= PostDat(1,y)
下一个
n = 2
对于iCntr = 2 To UBound(PreDat, 1)
ResDelete = True
对于y = 1到UBound(PreDat,2)
如果PreDat(iCntr,y) PostDat(iCntr,y)然后
ResDelete = False
退出
结束如果
下一个y
如果没有ResDelete然后
对于y = 1到UBound(PreDat,2)
PreDelDat(n,y)= PreDat(iCntr,y)
PostDelDat(n,y)= PostDat(iCntr,y)
下一个
n = n + 1
End If
Next iCntr
T2 = GetTickCount
Debug.PrintCompare Done in:,T2 - T1
Debug.Print要删除的行:,n - 1
rPre = PreDelDat
rPost = PostDelDat
T3 = GetTickCount
Debug.PrintDelete Done In: ,T3 - T1
清理:
'恢复optmizing行
在错误恢复下一步
ActiveSheet.DisplayPageBreaks = PageBreakState
Application.Calculation = CalcState
应用程序.EnableEvents = EventState
Application.ScreenUpdating = True
退出子
EH:
'Han dle错误在这里
Debug.Assert False
简历
Err.Clear
恢复清理
结束Sub
原始:
一个好的方法是复制数据在变量数组中,并循环遍历,构建对单元格的引用以稍后删除。然后一次删除。
其他一般提示:
- 声明 $ xlUp)以避免出现意外空白(除非您希望在第一个空白处停止)
重构代码:
Sub Demo()
Dim ResDelete As Boolean
Dim iPRE As Long,iPOST As Long
Dim EventState As Boolean,CalcState As XlCalculation,PageBreakState As Boolean
Dim iCntr As Long,y As Long,iRows As Long
Dim rPreDelete As Range,rPostDelete As范围
Dim PreDat As Variant,PostDat As Variant
使用ActiveWorkbook.Worksheets(PRE)
PreDat = .Range(.Cells(1,20) ,.Cells(.Rows.Count,1).End(xlUp))。值
iPRE = UBound(PreDat,1)
'MsgBox iPRE
结束
使用ActiveWorkbook.Works heets(POST)
PostDat = .Range(.Cells(1,20),.Cells(.Rows.Count,1).End(xlUp))值
iPOST = UBound(PostDat ,1)
'MsgBox iPOST
结束
如果iPRE<> iPOST然后
MsgBoxPRE和POST表中的行数不一样,宏退出
Exit Sub
End If
iRows = iPRE
错误GoTo EH:
'优化性能
Application.ScreenUpdating = False
EventState = Application.EnableEvents
Application.EnableEvents = False
CalcState = Application.Calculation
Application.Calculation = xlCalculationManual
PageBreakState = ActiveSheet.DisplayPageBreaks
ActiveSheet.DisplayPageBreaks = False
对于iCntr = 2 To UBound(PreDat,1)
ResDelete = True
对于y = 1到20
如果PreDat(iCntr,y) PostDat(iCntr,y)然后
ResDelete = False
退出
结束如果
下一个y
如果ResDelete然后
如果rPreDelete是没有,然后
设置rPreDelete =工作表(PRE)。行(iCntr)
设置rPostDelete =工作表(POST)。行(iCntr)
Else
设置rPreDelete = Application.Union(rPreDelete,Worksheets(PRE)。Rows(iCntr))
设置rPostDelete = Application.Union(rPostDelete,Worksheets(POST)。Rows(iCntr))
End If
结束如果
下一步iCntr
如果不是rPreDelete是没有,然后
rPreDelete.Delete
rPostDelete.Delete
结束如果
清理:
'还原optmizing行
在错误恢复下一步
ActiveSheet.DisplayPageBreaks = PageBreakState
Application.Calculation = CalcState
Application.EnableEvents = EventState
Application.ScreenUpdating = True
退出子
EH:
'处理错误
恢复清理
结束子
I am comparing sheets in a workbook. The workbook has two sheets named PRE and POST with the same 19 columns in each. The number of rows varies every day but are same for the two sheets on a particular day. The macro compares each row in the PRE sheet to the corresponding row in the POST sheet and deletes the rows in both sheets if they are identical.
I have the usually suggested methods of improving performance like screen updating set to FALSE etc.
I want to optimize the two FOR NEXT
loops.
Dim RESULT As String
iPRE = ActiveWorkbook.Worksheets("PRE").Range("A1", Worksheets("PRE").Range("A1").End(xlDown)).Rows.Count
'MsgBox iPRE
iPOST = ActiveWorkbook.Worksheets("POST").Range("A1", Worksheets("POST").Range("A1").End(xlDown)).Rows.Count
'MsgBox iPOST
If iPRE <> iPOST Then
MsgBox "The number of rows in PRE and POST sheets are not the same. The macro quits"
Exit Sub
Else
iRows = iPRE
End If
'Optimize Performance
Application.ScreenUpdating = False
EventState = Application.EnableEvents
Application.EnableEvents = False
CalcState = Application.Calculation
Application.Calculation = xlCalculationManual
PageBreakState = ActiveSheet.DisplayPageBreaks
ActiveSheet.DisplayPageBreaks = False
For iCntr = iRows To 2 Step -1
For y = 1 To 20
If Worksheets("PRE").Cells(iCntr, y) <> Worksheets("POST").Cells(iCntr, y) Then
RESULT = "DeleteN"
Exit For
Else
RESULT = "DeleteY"
End If
Next y
If RESULT = "DeleteY" Then
Worksheets("PRE").Rows(iCntr).Delete
Worksheets("POST").Rows(iCntr).Delete
End If
Next iCntr
'Revert optmizing lines
ActiveSheet.DisplayPageBreaks = PageBreakState
Application.Calculation = CalcState
Application.EnableEvents = EventState
Application.ScreenUpdating = True
End Sub
Any references to a worksheet cells is slow. This adds up dramatically when you do it in a loop. The best speed increase will come from limiting these worksheet references.
One good way is to copy the data in Variant Arrays, and loop over these, building a new Variant Array with the data to be kept. Then place the new array over the old in one go in one go.
Using a test data set of 200,000 rows, 20 columns, 50% text, 50% numbers, deleting 170,000 rows: this code runs in about 30s on my hardware
Sub Mine2()
Dim T1 As Long, T2 As Long, T3 As Long
Dim ResDelete As Boolean
Dim iPRE As Long, iPOST As Long
Dim EventState As Boolean, CalcState As XlCalculation, PageBreakState As Boolean
Dim iCntr As Long, y As Long, iRows As Long
Dim rPre As Range, rPost As Range
Dim PreDat As Variant, PostDat As Variant, PreDelDat As Variant, PostDelDat As Variant
Dim n As Long
Dim wsPre As Worksheet, wsPost As Worksheet
Set wsPre = ActiveWorkbook.Worksheets("PRE")
With wsPre
Set rPre = .Range(.Cells(1, .Columns.Count).End(xlToLeft), .Cells(.Rows.Count, 1).End(xlUp))
PreDat = rPre.Value
iPRE = UBound(PreDat, 1)
'MsgBox iPRE
End With
Set wsPost = ActiveWorkbook.Worksheets("POST")
With wsPost
Set rPost = .Range(.Cells(1, .Columns.Count).End(xlToLeft), .Cells(.Rows.Count, 1).End(xlUp))
PostDat = rPost.Value
iPOST = UBound(PostDat, 1)
'MsgBox iPOST
End With
If iPRE <> iPOST Then
MsgBox "The number of rows in PRE and POST sheets are not the same. The macro quits"
Exit Sub
End If
iRows = iPRE
ReDim PreDelDat(1 To UBound(PreDat, 1), 1 To UBound(PreDat, 2))
ReDim PostDelDat(1 To UBound(PostDat, 1), 1 To UBound(PostDat, 2))
n = 1
On Error GoTo EH:
'Optimize Performance
Application.ScreenUpdating = False
EventState = Application.EnableEvents
Application.EnableEvents = False
CalcState = Application.Calculation
Application.Calculation = xlCalculationManual
PageBreakState = ActiveSheet.DisplayPageBreaks
ActiveSheet.DisplayPageBreaks = False
T1 = GetTickCount
For y = 1 To UBound(PreDat, 2)
PreDelDat(1, y) = PreDat(1, y)
PostDelDat(1, y) = PostDat(1, y)
Next
n = 2
For iCntr = 2 To UBound(PreDat, 1)
ResDelete = True
For y = 1 To UBound(PreDat, 2)
If PreDat(iCntr, y) <> PostDat(iCntr, y) Then
ResDelete = False
Exit For
End If
Next y
If Not ResDelete Then
For y = 1 To UBound(PreDat, 2)
PreDelDat(n, y) = PreDat(iCntr, y)
PostDelDat(n, y) = PostDat(iCntr, y)
Next
n = n + 1
End If
Next iCntr
T2 = GetTickCount
Debug.Print "Compare Done in:", T2 - T1
Debug.Print "Rows to delete:", n - 1
rPre = PreDelDat
rPost = PostDelDat
T3 = GetTickCount
Debug.Print "Delete Done In:", T3 - T1
CleanUp:
'Revert optmizing lines
On Error Resume Next
ActiveSheet.DisplayPageBreaks = PageBreakState
Application.Calculation = CalcState
Application.EnableEvents = EventState
Application.ScreenUpdating = True
Exit Sub
EH:
' Handle Errors here
Debug.Assert False
Resume
Err.Clear
Resume CleanUp
End Sub
Original:
One good way is to copy the data in Variant Arrays, and loop over these, building a reference to cells to delete later. Then do the delete in one go.
Other general tips:
- Declare all variables
- Use more appropriate data types (Long, Boolean)
- Use
End(xlUp)
to avoid failing at unexpected blanks (unless you want to stop at the first blank)
Refactored code:
Sub Demo()
Dim ResDelete As Boolean
Dim iPRE As Long, iPOST As Long
Dim EventState As Boolean, CalcState As XlCalculation, PageBreakState As Boolean
Dim iCntr As Long, y As Long, iRows As Long
Dim rPreDelete As Range, rPostDelete As Range
Dim PreDat As Variant, PostDat As Variant
With ActiveWorkbook.Worksheets("PRE")
PreDat = .Range(.Cells(1, 20), .Cells(.Rows.Count, 1).End(xlUp)).Value
iPRE = UBound(PreDat, 1)
'MsgBox iPRE
End With
With ActiveWorkbook.Worksheets("POST")
PostDat = .Range(.Cells(1, 20), .Cells(.Rows.Count, 1).End(xlUp)).Value
iPOST = UBound(PostDat, 1)
'MsgBox iPOST
End With
If iPRE <> iPOST Then
MsgBox "The number of rows in PRE and POST sheets are not the same. The macro quits"
Exit Sub
End If
iRows = iPRE
On Error GoTo EH:
'Optimize Performance
Application.ScreenUpdating = False
EventState = Application.EnableEvents
Application.EnableEvents = False
CalcState = Application.Calculation
Application.Calculation = xlCalculationManual
PageBreakState = ActiveSheet.DisplayPageBreaks
ActiveSheet.DisplayPageBreaks = False
For iCntr = 2 To UBound(PreDat, 1)
ResDelete = True
For y = 1 To 20
If PreDat(iCntr, y) <> PostDat(iCntr, y) Then
ResDelete = False
Exit For
End If
Next y
If ResDelete Then
If rPreDelete Is Nothing Then
Set rPreDelete = Worksheets("PRE").Rows(iCntr)
Set rPostDelete = Worksheets("POST").Rows(iCntr)
Else
Set rPreDelete = Application.Union(rPreDelete, Worksheets("PRE").Rows(iCntr))
Set rPostDelete = Application.Union(rPostDelete, Worksheets("POST").Rows(iCntr))
End If
End If
Next iCntr
If Not rPreDelete Is Nothing Then
rPreDelete.Delete
rPostDelete.Delete
End If
CleanUp:
'Revert optmizing lines
On Error Resume Next
ActiveSheet.DisplayPageBreaks = PageBreakState
Application.Calculation = CalcState
Application.EnableEvents = EventState
Application.ScreenUpdating = True
Exit Sub
EH:
' Handle Errors here
Resume CleanUp
End Sub
这篇关于提高FOR循环的性能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!