这会产生更好的性能吗?

时间:2017-07-25 12:26:00

标签: vba performance excel-vba optimization excel

我有一个小困境。众所周知,按类型定义变量并避免使用变量是最明显的性能技巧。问题是我试图编写一个可以使用隐式类型参数(基本上是变体)的例程库。

以例如:

Sub Test(A As String) ' Implicit ByRef
    Debug.Print A
End Sub

没什么好疯的吧?如果我Test "ABC",它会按预期工作。当我尝试从数组(Test Array("ABC")(0))传递一个值,或者在链接时传递来自另一个例程的返回值时,会出现问题。我收到编译错误,说#34; ByRef参数类型不匹配"。

我需要这些例程来接受各种类型的参数,并在可能的情况下对它们进行类型转换。然后我想到了以下内容:

Sub Test(ByVal A As String) ' Explicit ByVal
    Debug.Print A
End Sub

现在它运作正常。所以最终,我的问题是:通过明确定义参数类型来实现性能提升是否值得通过使用ByVal复制参数值来实现性能损失的权衡?我知道会出现一个比另一个好的情况,但对于一般用法库,哪种方法更合适?

我已经设置了一个小基准。我并没有完全使用世界上最强大的计算机(Core i3-2120,32位Windows 7,4 GB RAM),所以我不能说这些将适用于其他设置。

Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long

Private Sub NoOp(ParamArray Self()) ' Adds a small overhead; not sure if there's a better built-in routine to use
End Sub

Private Sub ByReference(ByRef Argument As Variant) ' ByRef and As Variant are the default keywords if all keywords are ignored
    NoOp Argument
End Sub

Private Sub ByValue(ByVal Argument As String)
    NoOp Argument
End Sub

Private Sub Benchmark()
    Dim Index As Long, Argument As Variant: Argument = "ABC"
    A = GetTickCount / 1000
    For Index = 1 To 10000000
        ByReference Argument
    Next
    B = GetTickCount / 1000
    For Index = 1 To 10000000
        ByValue Argument
    Next
    Debug.Print B - A, (GetTickCount / 1000) - B ' Seconds; we get higher precision if we divide them before taking their differences
End Sub

结果:3.88499999999476 4.99199999999837

我们可以看到字符串的一个非常明显的性能损失(无论如何都是短的)。

现在,如果我将Argument更改为12345并将例程定义的参数类型更改为Long,我会得到:

结果:4.07200000000012 4.05599999999686

我不知道这是否在误差范围内,但它确实告诉我们不同类型的行为会有所不同。

您可以在自己的系统上尝试不同的类型,并告诉我们。

1 个答案:

答案 0 :(得分:1)

我做了一个快速测试,看看下面。拨打ByRef的速度更快,这也就不足为奇了,但是时间太长,通常你真的不必关心。使用三年前的笔记本电脑在Win7上进行测试,没什么特别的。

以100万次迭代运行,ByRef的结果为141 ms,ByVal的结果为281 ms。每隔一次更改用作参数的字符串会增加时间,但对于这两种方法,我认为这是由于字符串处理。

我的结论:根据您的需要选择调用方法,而不是速度。

Option Explicit

Private mlngStart As Long
Private Declare Function GetTickCount Lib "kernel32" () As Long

Sub test()

    testPerformance 1000000, False
    testPerformance 1000000, True

End Sub

Sub testPerformance(iterations, changeString)
    Dim s As String
    s = "Hello World, this is a really long string to test what will happen when we pass it by value"
    StartTimer
    Dim i As Long
    For i = 1 To iterations
        If changeString Then s = IIf(i Mod 2 = 0, "Hello world", "Hello World, this is a really long string to test what will happen when we pass it by value")
        Call f1(s)
    Next i
    Debug.Print iterations, changeString, "ByRef: " & EndTimer

    StartTimer
    For i = 1 To iterations
        If changeString Then s = IIf(i Mod 2 = 0, "Hello world", "Hello World, this is a really long string to test what will happen when we pass it by value")
        Call f2(s)
    Next i
    Debug.Print iterations, changeString, "ByVal: " & EndTimer

End Sub

Sub f1(ByRef s As String)
    '
    If s = "X" Then
        Debug.Print s
    End If
End Sub

Sub f2(ByVal s As String)
    If s = "X" Then
        Debug.Print s
    End If
End Sub

Public Sub StartTimer()
    mlngStart = GetTickCount
End Sub

Public Function EndTimer() As Long
    EndTimer = (GetTickCount - mlngStart)
End Function

输出:

 1000000      False         ByRef: 141
 1000000      False         ByVal: 281
 1000000      True          ByRef: 655
 1000000      True          ByVal: 764