Performance 在Excel VBA中迭代类

Performance 在Excel VBA中迭代类,performance,excel,vba,Performance,Excel,Vba,我已经为一个名为Terminal的实体创建了一个类模块。我有一个方法,通过遍历175个单独的工作表并从特定单元格中提取正确的数据来填充这个类。这个过程非常快(大约2秒),但是当我尝试将这些数据写回新的工作表时,需要花费更长的时间(45秒)。这个过程似乎至少应该和填充类一样快,因为它从来都不必离开工作表,但事实并非如此。下面是我将数据写入工作表的过程,我是否忽略了导致此过程运行如此缓慢的原因 Application.ScreenUpdating = False Dim rowNumber As

我已经为一个名为Terminal的实体创建了一个类模块。我有一个方法,通过遍历175个单独的工作表并从特定单元格中提取正确的数据来填充这个类。这个过程非常快(大约2秒),但是当我尝试将这些数据写回新的工作表时,需要花费更长的时间(45秒)。这个过程似乎至少应该和填充类一样快,因为它从来都不必离开工作表,但事实并非如此。下面是我将数据写入工作表的过程,我是否忽略了导致此过程运行如此缓慢的原因

Application.ScreenUpdating = False
 Dim rowNumber As Integer
 Dim colNumber As Integer
 Dim terminalCode As String
 Dim terminal As clsTerminal

 rowNumber = 7
 colNumber = 1

 For Each terminal In terminals

        'Add hyperlink to each terminal code
        Sheets("Terminal Summary").Hyperlinks.Add Anchor:=Cells(rowNumber, colNumber), Address:="", _
            SubAddress:=terminal.terminalCode + "!A1", TextToDisplay:=terminal.terminalCode

        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 1).Value = "Current"

        'Current period
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 2).Value = terminal.iBShipments
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 3).Value = terminal.oBShipments
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 4).Value = terminal.iBNetRevenue
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 5).Value = terminal.oBNetRevenue
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 6).Value = terminal.iBWeight
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 7).Value = terminal.oBWeight
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 8).Value = terminal.iBMileage
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 9).Value = terminal.oBMileage
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 10).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 11).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 12).FormulaR1C1 = "=IFERROR(RC[-8]/RC[-10],0)"
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 13).FormulaR1C1 = "=IFERROR(RC[-8]/RC[-10],0)"
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 14).FormulaR1C1 = "=IFERROR(RC[-10]/(RC[-8] / 100),0)"
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 15).FormulaR1C1 = "=IFERROR(RC[-10]/(RC[-8] / 100),0)"
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 16).FormulaR1C1 = "=IFERROR(RC[-12]/RC[-8],0)"
        Sheets("Terminal Summary").Cells(rowNumber, colNumber + 17).FormulaR1C1 = "=IFERROR(RC[-12]/RC[-8],0)"

        rowNumber = rowNumber + 1
    Next terminal
编辑
我应该注意到terminals是terminals类的集合,这类代码的一个常见缺陷是每次向电子表格写入数据时,Excel都会进行计算(它会评估工作簿中的所有公式,以查看是否需要使用新数据进行计算)

如果在循环之前禁用自动计算,然后在循环之后重新启用,则运行速度会更快:

Application.Calculation = xlCalculationManual
For Each terminal In terminals
...
Next terminal
Application.Calculation = xlCalculationAutomatic

您已经有了很大的节省(在编写时关闭自动计算),但还有一些其他小技巧需要记住,以备将来使用

首先,每次从VBA写入单元格时,VBA都会转到工作簿/wootksheet/cell地址并执行写入操作,从而导致开销。在一次调用中写入多个单元格只会导致一次开销。因此,将多个值打包到一个数组中并跨多个单元格写入该数组可以获得时间。几行不值得,但数百行值得努力

此外,每个“点”还有一点开销。“虚线”表达式中的术语,如
工作表(“终端摘要”)。单元格(rowNumber、colNumber+2)
需要Excel/VBA确定每个调用涉及哪些对象。在某些情况下(特别是在引用远程对象时),这种开销可能非常大。VB为我们提供了
With…End With
构造,以减少继续解析这些引用的需要:每个以点开头的表达式都会自动引用下一个最外层
With
中的对象

所以我们可能会得到这样的结果:

With Sheets("Terminal Summary")
    .Cells(rowNumber, colNumber + 2).Resize(1, 8) = terminal.InfoArray
    .Cells(rowNumber, colNumber + 10).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
    .Cells(rowNumber, colNumber + 11).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
    .Cells(rowNumber, colNumber + 12).FormulaR1C1 = "=IFERROR(RC[-8]/RC[-10],0)"
    .Cells(rowNumber, colNumber + 13).FormulaR1C1 = "=IFERROR(RC[-8]/RC[-10],0)"
    .Cells(rowNumber, colNumber + 14).FormulaR1C1 = "=IFERROR(RC[-10]/(RC[-8] / 100),0)"
    .Cells(rowNumber, colNumber + 15).FormulaR1C1 = "=IFERROR(RC[-10]/(RC[-8] / 100),0)"
    .Cells(rowNumber, colNumber + 16).FormulaR1C1 = "=IFERROR(RC[-12]/RC[-8],0)"
    .Cells(rowNumber, colNumber + 17).FormulaR1C1 = "=IFERROR(RC[-12]/RC[-8],0)"
End With
Public Property Get InfoArray() As Variant
    InfoArray = Array(iBShipments, oBShipments, iBNetRevenue, oBNetRevenue, iBWeight, oBWeight, iBMileage, oBMileage)
End Property
我会将数组内容塞进Terminal类中,类似这样:

With Sheets("Terminal Summary")
    .Cells(rowNumber, colNumber + 2).Resize(1, 8) = terminal.InfoArray
    .Cells(rowNumber, colNumber + 10).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
    .Cells(rowNumber, colNumber + 11).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
    .Cells(rowNumber, colNumber + 12).FormulaR1C1 = "=IFERROR(RC[-8]/RC[-10],0)"
    .Cells(rowNumber, colNumber + 13).FormulaR1C1 = "=IFERROR(RC[-8]/RC[-10],0)"
    .Cells(rowNumber, colNumber + 14).FormulaR1C1 = "=IFERROR(RC[-10]/(RC[-8] / 100),0)"
    .Cells(rowNumber, colNumber + 15).FormulaR1C1 = "=IFERROR(RC[-10]/(RC[-8] / 100),0)"
    .Cells(rowNumber, colNumber + 16).FormulaR1C1 = "=IFERROR(RC[-12]/RC[-8],0)"
    .Cells(rowNumber, colNumber + 17).FormulaR1C1 = "=IFERROR(RC[-12]/RC[-8],0)"
End With
Public Property Get InfoArray() As Variant
    InfoArray = Array(iBShipments, oBShipments, iBNetRevenue, oBNetRevenue, iBWeight, oBWeight, iBMileage, oBMileage)
End Property
在终端信息完成后,通过每列写入一次公式,可以更有效地输出公式


有些事情要考虑,至少…

你已经有90%的答案我会给,但这里有一个进一步的性能提示。而不是做:

Sheets("Terminal Summary").Cells(rowNumber, colNumber + 10).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"
您可以使用将同一公式分配给列中所有单元格的事实,并在一条语句中执行此操作:

Sheets("Terminal Summary").Cells(7, 11).Resize(terminals.Count, 1).FormulaR1C1 = "=IFERROR(RC[-4]/RC[-8],0)"

+1这些都是很好的技巧,除了代码的性能外,代码更简洁。我是一个在VBA工作的C#家伙,看起来我还有很多东西要学+1,谢谢。我不知道你能一次写出整个单元格块。这会很有用的。这就引出了我的最后一个问题,一次在一列中添加所有公式(或格式)是否比每次在逐行添加其他数据时通过该列要快得多?是的。如果您可以用VBA中的工作来换取Excel以本机方式完成,那么您将看到一个巨大的速度提升。因此,虽然对列的一次赋值可能比对一个单元格的一次赋值要长一些,但它比对100个单元格的一次赋值要快得多……哎呀,我希望我能编辑这个注释。我想说的是,比每个单元格分配100个作业要快得多。