Performance 提高FOR-loop的性能
我正在比较工作簿中的工作表。该工作簿有两张名为PRE和POST的工作表,每张工作表中有相同的19列。行数每天都不同,但在特定的一天,两张图纸的行数相同。宏将前工作表中的每一行与后工作表中的对应行进行比较,如果两个工作表中的行相同,则删除它们 我有一些通常建议的提高性能的方法,比如将屏幕更新设置为FALSE等 我想为下一个循环优化这两个Performance 提高FOR-loop的性能,performance,excel,vba,Performance,Excel,Vba,我正在比较工作簿中的工作表。该工作簿有两张名为PRE和POST的工作表,每张工作表中有相同的19列。行数每天都不同,但在特定的一天,两张图纸的行数相同。宏将前工作表中的每一行与后工作表中的对应行进行比较,如果两个工作表中的行相同,则删除它们 我有一些通常建议的提高性能的方法,比如将屏幕更新设置为FALSE等 我想为下一个循环优化这两个 Dim RESULT As String iPRE = ActiveWorkbook.Worksheets("PRE").Range("A1", Workshe
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
Dim结果为字符串
iPRE=ActiveWorkbook.Worksheets(“PRE”).Range(“A1”),Worksheets(“PRE”).Range(“A1”).End(xlDown)).Rows.Count
'MsgBox iPRE
iPOST=active工作簿.Worksheets(“POST”).Range(“A1”,Worksheets(“POST”).Range(“A1”).End(xlDown)).Rows.Count
'MsgBox iPOST
如果我是iPOST那么
MsgBox“预工作表和后工作表中的行数不相同。宏退出”
出口接头
其他的
iRows=iPRE
如果结束
“优化性能
Application.ScreenUpdating=False
EventState=Application.EnableEvents
Application.EnableEvents=False
CalcState=应用程序计算
Application.Calculation=xlCalculationManual
PageBreakState=ActiveSheet.DisplayPageBreaks
ActiveSheet.DisplayPageBreaks=False
对于iCntr=iRows至2步骤-1
对于y=1到20
如果工作表(“前”)单元格(iCntr,y)工作表(“后”)单元格(iCntr,y),则
RESULT=“DeleteN”
退出
其他的
RESULT=“DeleteY”
如果结束
下一个y
如果RESULT=“DeleteY”,则
工作表(“预”)行(iCntr)。删除
工作表(“POST”)。行(iCntr)。删除
如果结束
下一个iCntr
'还原优化行
ActiveSheet.DisplayPageBreaks=PageBreakState
Application.Calculation=CalcState
Application.EnableEvents=EventState
Application.ScreenUpdating=True
端接头
也许您可以进行两次调整,尽管它们对性能的影响非常小:
“准备对工作表的引用
将工作表变暗为工作表
将工作表标题设置为工作表
Set WorksheetPRE=ActiveWorkbook.Worksheets(“PRE”)
Set WorksheetPOST=ActiveWorkbook.Worksheets(“POST”)
然后,在代码中,替换ActiveWorkbook.Worksheets(“PRE”)
的WorksheetPRE
等
我认为,当您停留在Excel中时,不可能进行其他重要的优化。请记住,Microsoft Excel主要是一个计算器,而不是一个数据表处理工具
如果我真的需要加速比较,那么我会采用以下方法之一:
- 将Excel工作表链接到Microsoft Access as表格,并在Access中执行比较(最简单)
- 如上所述,但不是链接表,而是导入它
- 如上两种,但使用Microsoft SQL Server(Express版本是免费的)
对工作表单元格的任何引用都很慢。当你在一个循环中做这件事时,它会显著增加。最好的速度提升来自于限制这些工作表引用
一个好方法是复制变量数组中的数据,并循环这些数据,用要保留的数据构建一个新的变量数组。然后将新阵列一次性放置在旧阵列上
使用200000行、20列、50%文本、50%数字的测试数据集,删除170000行:这段代码在我的硬件上运行大约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
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
如果我可以把我的两分钱放进去,这是我的建议
我已经测试了原始代码(唯一的改动是y=1到10的,而不是y=1到20的),并将我的代码和两张10列(最初为500000)250000行的数据表进行对比。我之所以使用10而不是20,是因为我不知道列中的数据是什么,作为替代,我使用了随机值1或2
- 对于10列,这意味着存在
2^10=1024
可能性
- 对于20列,这意味着存在
2^20=1048576
可能性
由于我希望每个表中至少有几个相等的行,所以我选择了10列方案
为了给宏计时,我设置了一个定时器宏,它调用宏来比较和删除数据
为了能够比较结果,在启动Excel并打开具有完全相同数据的文件后,直接执行两个宏
我有
- 避免了
活动的所有实例
- 最小化Excel和VBA之间的数据读取和写入,这是通过在二维数组中收集图纸上的所有数据,然后分析数组来实现的
- 收集范围内要删除的行(每张1行),并删除循环外部要删除的所有行
代码
结果
我的代码-500000行数据。
包含500.000行和10列的数据表已在14,23秒内处理完毕,561行被发现相等,并已被删除
原始代码-500000行数据。
不幸的是,我的系统无法处理此任务,Excel停止工作。
我的代码-250000行数据。
在4.72秒内处理了250.000行10列的数据表,发现313行相等,并已删除
原始代码-250000行数据。
在14.07秒内处理了250.000行和10列的数据表,发现313行相等并已删除
删除行(逐行)速度较慢,请尝试使用Union
并一次性删除所有行,例如,如果宏将删除1000行,则使用Union
将快1000倍,但如果要删除1或2行,此方法将没有帮助。这与我的代码在功能上完全相同。谢谢你的测试。看来你是对的@chrisneilsen。
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
Sub CompareAndDelete()
Dim WsPre As Worksheet, WsPost As Worksheet
Dim Row As Long, Column As Long
Dim ArrPre() As Variant, ArrPost() As Variant
Dim DeleteRow As Boolean
Dim DeletePre As Range, DeletePost As Range
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
.EnableEvents = False
End With
With ThisWorkbook
Set WsPre = .Worksheets("PRE")
Set WsPost = .Worksheets("Post")
End With
ArrPre = WsPre.Range(WsPre.Cells(1, 1), WsPre.Cells(WsPre.Cells(WsPre.Rows.Count, 1).End(xlUp).Row, 20))
ArrPost = WsPost.Range(WsPost.Cells(1, 1), WsPost.Cells(WsPost.Cells(WsPost.Rows.Count, 1).End(xlUp).Row, 20))
If Not UBound(ArrPre, 1) = UBound(ArrPost, 1) Then
MsgBox "Unequal number of rows in sheets PRE and POST. Exiting macro.", vbCritical, "Unequal sheets"
Else
For Row = 2 To UBound(ArrPre, 1)
DeleteRow = True
For Column = 1 To UBound(ArrPre, 2)
If Not ArrPre(Row, Column) = ArrPost(Row, Column) Then
DeleteRow = False
Exit For
End If
Next Column
If DeleteRow = True Then
If DeletePre Is Nothing Then
Set DeletePre = WsPre.Rows(Row)
Set DeletePost = WsPost.Rows(Row)
Else
Set DeletePre = Union(DeletePre, WsPre.Rows(Row))
Set DeletePost = Union(DeletePost, WsPost.Rows(Row))
End If
End If
Next Row
If Not DeletePre Is Nothing Then DeletePre.Delete
If Not DeletePost Is Nothing Then DeletePost.Delete
End If
With Application
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
.EnableEvents = True
End With
End Sub