【问题标题】:ByRef vs ByVal performance when passing strings传递字符串时的 ByRef 与 ByVal 性能
【发布时间】:2010-07-22 11:03:35
【问题描述】:

阅读Which is faster? ByVal or ByRef? 让我想知道那里的 cmets 在性能方面是否确实适用于Strings。由于字符串是在传递之前复制的,所以传递字符串ByRef 不是更有效(如果被调用者不需要字符串的副本)吗?

谢谢,
CFP。

编辑:考虑一下这段代码,这让我觉得有某种副本正在进行:

Sub Main()
    Dim ByValStr As String = "Hello World (ByVal)!"
    Dim ByRefStr As String = "Hello World (ByRef)!"

    fooval(ByValStr)
    fooref(ByRefStr)

    Console.WriteLine("ByVal: " & ByValStr)
    Console.WriteLine("ByRef: " & ByRefStr)

    Console.ReadLine()
End Sub


Sub fooval(ByVal Str As String)
    Str = "foobar"
End Sub

Sub fooref(ByRef Str As String)
    Str = "foobar"
End Sub

它输出:

ByVal: Hello World (ByVal)!
ByRef: foobar

【问题讨论】:

    标签: vb.net performance string byref byval


    【解决方案1】:

    字符串在传递之前不会被复制。字符串是引用类型,尽管它们的行为有点像值类型。

    您应该根据您的要求使用最有意义的任何东西。 (如果您的要求恰好是“必须以牺牲所有其他考虑为代价来压缩最后一纳秒的性能”,那么您可能应该破解分析器而不是询问 stackoverflow!)

    这几乎肯定是您无需担心的事情,而且我怀疑是否存在显着的性能差异。我可以看到任何差异的唯一情况是传递 big 值类型。

    【讨论】:

    • 然而,如果你将ByVal 一个字符串mystr 传递给foobar 并在foobar 中设置mystr = "hello",那么mystr 的初始值不会改变...所以肯定有某种副本正在进行,不是吗?
    • 字符串是引用类型,因此无论您使用ByVal 还是ByRef,您都在传递引用。不同之处在于,当您传递ByVal 时,您传递的是一个包含原始引用副本的新变量,因此对该变量的任何更改本质上都是私有的;当您传递ByRef 时,您将传递原始变量,因此对该变量的任何更改都在外部可见。
    • 这意味着当您传递ByVal 并设置mystr = "hello" 时,您只是将变量mystr 中包含的指针覆盖为指向"hello",但不会更改原始指针自己?
    • 好吧,对新字符串“hello”的引用被分配给传递的变量——如果你传递了ByRef,那么该变量就是原始的外部变量;如果你传递了ByVal,那么它只是方法的本地变量,原始的外部变量不受影响。
    • 这就是为什么字符串在 .NET 中是不可变的;如果无法更改底层内存内容,那么运行时可以毫无顾忌地将字符串作为引用传递。
    【解决方案2】:

    我决定自己检查一下,以获得更“科学”的答案。他们是一样的。如果我使用下面的代码,ByVal 比 ByRef 慢 2%。但是,如果我交换它们,以便我在 ByVal 之前计时 ByRef,那么 ByRef 会慢 2% 左右。因此,在这种情况下,比 ByRef 或 ByVal 更重要的是它们的运行顺序:)

    Function CreateString()
    
        Dim i As Integer
        Dim str As String = ""
    
        For i = 1 To 10000
            str = str & "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        Next i
    
        Return str
    End Function
    
    Sub fooval(ByVal Str As String)
        Str = Str & "foobar"
    End Sub
    
    Sub fooref(ByRef Str As String)
        Str = Str & "foobar"
    End Sub
    
    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Dim str As String = CreateString()
        Dim stopWatch As New Stopwatch
        Dim i As Integer
    
        stopWatch.Start()
        For i = 1 To 1000
            fooval(str)
        Next i
        stopWatch.Stop()
        Dim valtime As Long = stopWatch.ElapsedMilliseconds
    
        stopWatch.Restart()
        For i = 1 To 1000
            fooref(str)
        Next i
        stopWatch.Stop()
        Dim reftime As Long = stopWatch.ElapsedMilliseconds
    
        MsgBox("Val took " & valtime & " milliseconds and Ref took " & reftime & " milliseconds")
    End Sub
    

    【讨论】:

      【解决方案3】:

      要了解包括字符串在内的类类型的行为,请将所有类类型参数、变量、字段和数组元素等视为持有“对象ID”。如果Foostring 类型的变量,则语句Foo = 12345.ToString(); 将创建一个新的对象ID(假设为对象ID#197),并创建一个具有该ID 的string 类型的新对象,包含五个字符"12345"。然后它将Object ID#197 存储到变量Foo 中。如果调用带有非引用参数param 的例程,并将Foo 传递给它,那么param 将是一个持有Object ID #197 的局部变量。语句param += "6"; 将创建一个字符串类型的新对象(例如对象ID #521),包含六个字符"123456" 并将Object ID #521 存储到param 中。请注意,Foo 仍然包含 Object ID#197,并且该对象仍然包含五个字符的字符串 "12345"

      如果param 已被ref 传递,那么语句param += "6" 会将Object ID #521 存储到Foo 中。它仍然不会对 Object #197 造成任何可观察到的更改,除非可能使其符合垃圾回收条件(如果 Foo 是对 Object #197 的唯一引用,则覆盖它意味着将不再存在任何引用到宇宙中任何地方的那个物体)。

      请注意,对于像string 这样的不可变类类型,通常很容易推理,即使不考虑对象 ID,因为更改字符串变量表示的字符序列的唯一方法是存储不同的对象身份证在那里。然而,在处理可变类类型时,考虑对象 ID 变得至关重要。传递 Car 类类型的变量,而不是通过 ref,相当于将 VIN 从一张纸复制到另一张纸,然后将后一张纸交给一些车间工人,并要求他们用它做点什么。如果第一篇论文最初识别出一辆带有 VIN#15934 的红色汽车,那么当工人完成后,第一篇论文可能会识别出一辆带有 VIN#15934 的蓝色汽车,但它会是同一辆车。工人们不能用他们得到的纸条做任何事情,也不能用汽车做任何事情,都不会改变第一张纸所指的汽车。另一方面,通过引用传递参数更像是车间工人一张写有 VIN 的纸,完成后从他们那里取回纸。如果工人可以划掉 VIN 并写另一个,那么当他们退回纸条时,它可能指的是同一辆车或另一辆车;如果它指的是不同的汽车,那么它最初所指的汽车可能已经过改装,也可能没有,而论文最终提到的汽车可能与原车有任何相似之处。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-03-11
        • 1970-01-01
        • 2018-10-14
        • 1970-01-01
        • 2011-07-07
        • 2015-02-15
        • 1970-01-01
        • 2014-07-20
        相关资源
        最近更新 更多