在 VB.NET 中,哪个更快用于方法参数,ByVal
或者ByRef
?
此外,哪个在运行时(RAM)消耗更多资源?
我通读了这个问题,但答案并不适用或不够具体。
Byval 和 ByRef 参数应该根据需求和关于它们如何工作的知识而不是速度来使用。
http://www.developer.com/net/vb/article.php/3669066
回应斯劳的评论——
哪个在运行时消耗更多资源?
参数在堆栈上传递。堆栈非常快,因为它的内存分配只是一个指针增量,用于保留新的“帧”或“分配记录”。如果使用任何“堆栈”空间来传递参数,大多数 .NET 参数不会超过机器寄存器的大小。事实上,基本类型和指针都是在堆栈上分配的。.NET 中的堆栈大小限制为 1 MB。这应该让您了解参数传递消耗的资源有多少。
您可能会发现这一系列文章很有趣:
哪个更快?ByVal 或 ByRef。
准确和准确地测量充其量是困难的 - 取决于您测量的上下文,但我编写的一个基准测试调用了 1 亿次方法得出以下结果:
Public Sub Method1(ByRef s As String)
Dim c As String = s
End Sub
Public Sub Method2(ByVal s As String)
Dim c As String = s
End Sub
Public Sub Method3(ByRef i As Integer)
Dim x As Integer = i
End Sub
Public Sub Method4(ByVal i As Integer)
Dim x As Integer = i
End Sub
Sub Main()
Dim s As String = "Hello World!"
Dim k As Integer = 5
Dim t As New Stopwatch
t.Reset()
t.Start()
For i As Integer = 0 To 100000000
Method1(s)
Next
t.Stop()
Console.WriteLine("Reference Type - ByRef " & t.ElapsedMilliseconds)
t.Reset()
t.Start()
For i As Integer = 0 To 100000000
Method2(s)
Next
t.Stop()
Console.WriteLine("Reference Type - ByVal " & t.ElapsedMilliseconds)
t.Reset()
t.Start()
For i As Integer = 0 To 100000000
Method3(i)
Next
t.Stop()
Console.WriteLine("Value Type - ByRef " & t.ElapsedMilliseconds)
t.Reset()
t.Start()
For i As Integer = 0 To 100000000
Method4(i)
Next
t.Stop()
Console.WriteLine("Value Type - ByVal " & t.ElapsedMilliseconds)
Console.ReadKey()
End Sub
注释掉每种方法中的变量和赋值 -
可以得出结论,通过 ByVal 传递引用类型(字符串、类)将节省一些时间。您也可能会说传递值类型(整数、字节)- ByVal 会节省一些时间。
再一次,时间在宏伟的计划中可以忽略不计。更重要的是正确使用 ByVal 和 ByRef 并了解“幕后”发生的事情。在您的例程中实现的算法肯定会多倍地影响您的程序的运行时间。
如果您使用的是非常大的值类型(例如,Guid 非常大),通过引用传递参数可能会稍微快一些。在其他情况下,当您通过引用而不是通过值传递时,可能会有更多的复制等 - 例如,如果您有一个字节参数,那么一个字节显然小于指针将占用的四个或八个字节如果您通过引用传递它。
在实践中,您几乎不应该担心这一点。尽可能编写可读性最强的代码,这几乎总是意味着按值而不是引用传递参数。我很少使用 ByRef。
如果您想提高性能并认为 ByRef 会帮助您,请在提交之前仔细(在您的确切情况下)对其进行基准测试。
编辑:我在对另一个(以前接受,现在已删除)答案的评论中注意到,在值类型方面,对于 ByRef 与 ByVal 的含义存在很多误解。我有一篇关于参数传递的文章,多年来证明它很受欢迎——它使用 C# 术语,但同样的概念也适用于 VB.NET。
这取决于。如果您正在传递一个对象,那么它已经在传递一个指针。这就是为什么如果你传入一个 ArrayList(例如)并且你的方法向 ArrayList 添加了一些东西,那么调用代码也会在传入的 ArrayList 中有相同的对象,因为它是相同的 ArrayList。唯一不传递指针的情况是,当您将具有内在数据类型(如 int 或 double)的变量传递给函数时。此时,它会创建一个副本。但是,这些对象的数据量非常小,无论是在内存使用量还是执行速度方面,它几乎都不会产生影响。
如果传入引用类型,ByRef 会更慢。
这是因为传入的是指向指针的指针。对对象上字段的任何访问都需要取消引用一个额外的指针,这将需要几个额外的时钟周期才能完成。
如果你传递的是值类型,那么如果结构有很多成员,那么 byref 可能会更快,因为它只传递一个指针,而不是复制堆栈上的值。在访问成员方面,byref 会慢一些,因为它需要做一个额外的指针解引用(sp->pValueType->member vs sp->member)。
大多数情况下,在 VB 中您不必担心这一点。
在 .NET 中,很少有具有大量成员的值类型。它们通常很小。在这种情况下,传入一个值类型与将多个参数传入一个过程没有什么不同。例如,如果您的代码按值传入 Point 对象,则其性能与将 X 和 Y 值作为参数的方法相同。看到 DoSomething(x as integer, y as integer) 可能不会引起性能问题。事实上,你可能永远不会三思而后行。
如果您自己定义大值类型,那么您可能应该重新考虑将它们转换为引用类型。
唯一的另一个区别是执行代码所需的指针间接次数增加。您很少需要在该级别进行优化。大多数时候,您可以解决算法问题,或者您的性能瓶颈与 IO 相关,例如等待数据库或写入文件,在这种情况下,消除指针间接对您没有太大帮助。
因此,与其关注 byval 还是 byref 哪个更快,我建议您真正关注的是什么为您提供了所需的语义。通常,除非您特别需要 byref,否则使用 byval 是个好主意。它使程序更容易理解。
虽然我对 .NET 的内部了解不多,但我将讨论我对编译语言的了解。这不适用于引用类型,并且对于值类型可能并不完全准确。如果您不知道值类型和引用类型之间的区别,则不应阅读此内容。我假设是 32 位 x86(带有 32 位指针)。
判决:
了解 ByVal 和 ByRef 实际为您做了什么以及了解值类型和引用类型之间的区别比考虑性能要重要得多。第一条规则是使用更适合您的代码的方法。
对于大值类型(超过 64 位),按引用传递,除非按值传递有优势(例如更简单的代码、“它才有意义”或接口一致性)。
对于较小的值类型,传递机制对性能没有太大影响,而且无论如何很难预测哪种方法会更快,因为它取决于对象大小、调用者和被调用者如何使用对象,甚至缓存注意事项. 只需为您的代码做任何有意义的事情。
ByVal
创建变量的副本,同时ByRef
传递一个指针。因此,我会说这ByVal
更慢(由于复制需要时间)并且使用更多内存。
我的好奇心是根据对象和内存使用情况检查不同的行为
结果似乎表明 ByVal 总是获胜,资源取决于是否收集内存或更少(仅限 4.5.1)
Public Structure rStruct
Public v1 As Integer
Public v2 As String
End Structure
Public Class tClass
Public v1 As Integer
Public v2 As String
End Class
Public Sub Method1(ByRef s As String)
Dim c As String = s
End Sub
Public Sub Method2(ByVal s As String)
Dim c As String = s
End Sub
Public Sub Method3(ByRef i As Integer)
Dim x As Integer = i
End Sub
Public Sub Method4(ByVal i As Integer)
Dim x As Integer = i
End Sub
Public Sub Method5(ByVal st As rStruct)
Dim x As rStruct = st
End Sub
Public Sub Method6(ByRef st As rStruct)
Dim x As rStruct = st
End Sub
Public Sub Method7(ByVal cs As tClass)
Dim x As tClass = cs
End Sub
Public Sub Method8(ByRef cs As tClass)
Dim x As tClass = cs
End Sub
Sub DoTest()
Dim s As String = "Hello World!"
Dim cs As New tClass
cs.v1 = 1
cs.v2 = s
Dim rt As New rStruct
rt.v1 = 1
rt.v2 = s
Dim k As Integer = 5
ListBox1.Items.Add("BEGIN")
Dim t As New Stopwatch
Dim gt As New Stopwatch
If CheckBox1.Checked Then
ListBox1.Items.Add("Using Garbage Collection")
System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.GetTotalMemory(False)
End If
Dim d As Double = GC.GetTotalMemory(False)
ListBox1.Items.Add("Free Memory: " & d)
gt.Start()
t.Reset()
t.Start()
For i As Integer = 0 To 100000000
Method1(s)
Next
t.Stop()
ListBox1.Items.Add("Reference Type - ByRef " & t.ElapsedMilliseconds)
t.Reset()
t.Start()
For i As Integer = 0 To 100000000
Method2(s)
Next
t.Stop()
ListBox1.Items.Add("Reference Type - ByVal " & t.ElapsedMilliseconds)
t.Reset()
t.Start()
For i As Integer = 0 To 100000000
Method3(i)
Next
t.Stop()
ListBox1.Items.Add("Value Type - ByRef " & t.ElapsedMilliseconds)
t.Reset()
t.Start()
For i As Integer = 0 To 100000000
Method4(i)
Next
t.Stop()
ListBox1.Items.Add("Value Type - ByVal " & t.ElapsedMilliseconds)
t.Reset()
t.Start()
For i As Integer = 0 To 100000000
Method5(rt)
Next
t.Stop()
ListBox1.Items.Add("Structure Type - ByVal " & t.ElapsedMilliseconds)
t.Reset()
t.Start()
For i As Integer = 0 To 100000000
Method6(rt)
Next
t.Stop()
ListBox1.Items.Add("Structure Type - ByRef " & t.ElapsedMilliseconds)
t.Reset()
t.Start()
For i As Integer = 0 To 100000000
Method7(cs)
Next
t.Stop()
ListBox1.Items.Add("Class Type - ByVal " & t.ElapsedMilliseconds)
t.Reset()
t.Start()
For i As Integer = 0 To 100000000
Method8(cs)
Next
t.Stop()
gt.Stop()
ListBox1.Items.Add("Class Type - ByRef " & t.ElapsedMilliseconds)
ListBox1.Items.Add("Total time " & gt.ElapsedMilliseconds)
d = GC.GetTotalMemory(True) - d
ListBox1.Items.Add("Total Memory Heap consuming (bytes)" & d)
ListBox1.Items.Add("END")
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
DoTest()
End Sub