it-swarm.com.de

Welche ist schneller? ByVal oder ByRef?

Welche Methode ist in VB.NET schneller für Methodenargumente zu verwenden: ByVal oder ByRef?

Wer verbraucht zur Laufzeit mehr Ressourcen (RAM)?


Ich habe diese Frage gelesen, aber die Antworten sind nicht zutreffend oder spezifisch genug.

35
Robinicks

Byval- und ByRef-Argumente sollten basierend auf den Anforderungen und dem Wissen über ihre Funktionsweise verwendet werden nicht in Bezug auf die Geschwindigkeit.

http://www.developer.com/net/vb/article.php/3669066

Als Antwort auf einen Kommentar von Slough -

Was verbraucht zur Laufzeit mehr Ressourcen?

Parameter werden an den Stapel übergeben. Der Stapel ist sehr schnell, weil seine Speicherzuordnung einfach ein Zeigerinkrement ist, um einen neuen "Rahmen" oder "Zuordnungssatz" zu reservieren. Die meisten .NET-Parameter überschreiten die Größe eines Maschinenregisters nicht so wenig, wenn zum Übergeben von Parametern ein Stack-Speicherplatz verwendet wird. Tatsächlich werden Basistypen und Zeiger auf dem Stack zugewiesen. Die Stapelgröße in .NET ist auf 1 MB begrenzt. Dies sollte Ihnen einen Eindruck davon vermitteln, wie wenig Ressourcen durch die Parameterübergabe verbraucht werden.

Sie können diese Artikelserie interessant finden:

Verbesserung der Leistung durch Stapelzuordnung (.NET-Speicherverwaltung: Teil 2) 

Was ist schneller? ByVal oder ByRef.

Es ist im besten Fall schwierig, genau und fair zu messen - abhängig vom Kontext Ihrer Messung, aber ein Benchmark, den ich geschrieben habe, als er eine Methode 100 Millionen Mal aufgerufen hat, kam zu folgendem Ergebnis:

  • Reference Type - Passed ByRef: 420 ms
  • Reference Type - Passed ByVal: 382 ms
  • Value Type - Passed ByRef: 421 ms
  • Value Type - Passed ByVal: 416 ms
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

Die Variable und die Zuordnung in jeder Methode auskommentieren -

  • Reference Type - Passed ByRef: 389 ms
  • Reference Type - Passed ByVal: 349 ms
  • Value Type - Passed ByRef: 416 ms
  • Value Type - Passed ByVal: 385 ms

Man könnte daraus schließen, dass das Übergeben von Referenztypen (Zeichenfolgen, Klassen) ByVal einige Zeit spart. Sie können auch sagen, dass die Übergabe von Werttypen (Ganzzahl, Byte) - ByVal etwas Zeit spart.

Wieder ist die Zeit im großen System der Dinge vernachlässigbar. Wichtiger ist, ByVal und ByRef richtig einzusetzen und zu verstehen, was "hinter den Kulissen" los ist. Die in Ihren Routinen implementierten Algorithmen beeinflussen die Laufzeit Ihres Programms höchstwahrscheinlich um ein Vielfaches.

122
user50612

Wenn Sie einen sehr großen Werttyp verwenden (z. B. Guid ist ziemlich groß), ist es möglicherweise etwas schneller, einen Parameter als Referenz zu übergeben. In anderen Fällen kann es sein, dass more kopiert wird usw., wenn Sie als Referenz einen Wert übergeben. Wenn Sie beispielsweise einen Byte-Parameter haben, ist ein Byte deutlich weniger als die vier oder acht Byte Der Zeiger würde nehmen, wenn Sie ihn als Referenz übergeben.

In der Praxis sollte man sich fast nie Sorgen machen. Schreiben Sie den größtmöglichen lesbaren - Code, was fast immer bedeutet, dass Parameter statt Wert als Wert übergeben werden. Ich benutze ByRef sehr selten.

Wenn Sie die Leistung verbessern möchten und glauben, dass ByRef Ihnen helfen wird, sollten Sie please dies sorgfältig prüfen (in Ihrer genauen Situation), bevor Sie sich dazu verpflichten.

BEARBEITEN: Ich stelle in den Kommentaren zu einer anderen (zuvor akzeptierten, jetzt gelöschten) Antwort fest, dass es eine Menge Missverständnisse darüber gibt, was ByRef vs. ByVal für Werttypen bedeutet. Ich habe einen Artikel über Parameter-Passing , der sich im Laufe der Jahre als populär erwiesen hat - er ist in der C # -Terminologie, aber dieselben Konzepte gelten für VB.NET.

30
Jon Skeet

Es hängt davon ab, ob. Wenn Sie ein Objekt übergeben, wird bereits ein Zeiger übergeben. Wenn Sie beispielsweise eine ArrayList übergeben und Ihre Methode der ArrayList etwas hinzufügt, hat der aufrufende Code auch das gleiche Objekt in seiner ArrayList, das übergeben wurde, da es sich um dieselbe ArrayList handelt. Der einzige Zeitpunkt, an dem kein Zeiger übergeben wird, ist, wenn Sie eine Variable mit einem intrinsischen Datentyp, z. B. einem int oder einem double, an die Funktion übergeben. An diesem Punkt wird eine Kopie erstellt. Die Datengröße dieser Objekte ist jedoch so klein, dass sie hinsichtlich der Speichernutzung oder der Ausführungsgeschwindigkeit kaum einen Unterschied machen würde.

11
Kibbee

Wenn Sie einen Referenztyp übergeben, ist ByRef langsamer.

Dies liegt daran, dass ein Zeiger auf einen Zeiger übergeben wird. Jeder Zugriff auf Felder im Objekt erfordert die Deferenzierung eines zusätzlichen Zeigers. Dies dauert einige zusätzliche Taktzyklen.

Wenn Sie einen Werttyp übergeben, ist byref möglicherweise schneller, wenn die Struktur viele Member hat, da nur ein einzelner Zeiger übergeben wird, anstatt die Werte im Stapel zu kopieren. In Bezug auf den Zugriff auf Mitglieder ist byref langsamer, da es eine zusätzliche Zeigerdereferenzierung erforderlich macht (sp-> pValueType-> member vs sp-> member).

Die meiste Zeit in VB sollten Sie sich keine Sorgen machen.

In .NET gibt es selten Werttypen mit einer großen Anzahl von Mitgliedern. Sie sind normalerweise klein. In diesem Fall unterscheidet sich die Übergabe eines Werttyps nicht von der Übergabe mehrerer Argumente an eine Prozedur. Wenn Sie beispielsweise Code erhalten haben, der ein Point-Objekt nach Wert übergeben hat, ist seine Leistung identisch mit einer Methode, die X- und Y-Werte als Parameter verwendet. DoSomething (x als Ganzzahl, y als Ganzzahl) würde wahrscheinlich keine Bedenken hinsichtlich Perfektion verursachen. In der Tat würden Sie wahrscheinlich nie zweimal darüber nachdenken.

Wenn Sie große Werttypen selbst definieren, sollten Sie es sich wahrscheinlich noch einmal überlegen, diese in Referenztypen umzuwandeln.

Der einzige andere Unterschied ist die Erhöhung der Anzahl von Zeiger-Indirektionen, die zur Ausführung des Codes erforderlich sind. Es ist selten, dass Sie jemals auf dieser Ebene optimieren müssen. Meistens gibt es entweder algorithmische Probleme, die Sie ansprechen können, oder Ihr Engpass bei der Perfomance hängt mit IO zusammen, z. B. Warten auf Eine Datenbank oder das Schreiben in eine Datei. In diesem Fall hilft das Entfernen von Zeiger-Indirektionen nicht viel.

Anstatt sich darauf zu konzentrieren, ob byval oder byref schneller ist, würde ich Ihnen empfehlen, sich auf das zu konzentrieren, was Ihnen die Semantik gibt, die Sie brauchen. Im Allgemeinen ist es ratsam, byval zu verwenden, es sei denn, Sie benötigen byref ausdrücklich. Es macht das Programm viel einfacher zu verstehen.

5

Obwohl ich nicht viel über die Interna von .NET weiß, werde ich darüber sprechen, was ich über kompilierte Sprachen weiß. Dieses gilt nicht für Referenztypen und ist möglicherweise nicht vollständig in Bezug auf Werttypen. Wenn Sie den Unterschied zwischen Werttypen und Referenztypen nicht kennen, sollten Sie dies nicht lesen. Ich gehe von 32-Bit x86 (mit 32-Bit-Zeigern) aus.

  • Beim Übergeben von Werten unter 32 Bit wird immer noch ein 32-Bit-Objekt auf dem Stapel verwendet. Ein Teil dieses Objekts wird "nicht verwendet" oder "Auffüllen" sein. Das Übergeben solcher Werte erfordert nicht weniger Speicher als das Übergeben von 32-Bit-Werten.
  • Beim Übergeben von Werten, die größer als 32 Bit sind, wird mehr Stapelspeicher als ein Zeiger und möglicherweise mehr Kopierzeit benötigt.
  • Wenn ein Objekt als Wert übergeben wird, kann der Aufgerufene das Objekt vom Stapel abrufen. Wenn ein Objekt als Referenz übergeben wird, muss der Aufgerufene zuerst die Adresse des Objekts aus dem Stapel abrufen und dann den Wert des Objekts an anderer Stelle abrufen. Wert bedeutet einen Abruf weniger, oder? Nun, der Abruf muss tatsächlich vom Anrufer ausgeführt werden. Der Anrufer musste jedoch möglicherweise bereits aus verschiedenen Gründen einen Abruf durchführen. In diesem Fall wird ein Abruf gespeichert.
  • Offensichtlich müssen alle Änderungen, die an einem Nachbezugswert vorgenommen werden, im RAM gespeichert werden, während ein Nachwertparameter verworfen werden kann.
  • Es ist besser, als Wert zu übergeben, als als Referenz, nur um den Parameter in eine lokale Variable zu kopieren und ihn nicht erneut zu berühren.

Das Urteil:

Es ist viel wichtiger zu verstehen, was ByVal und ByRef tatsächlich für Sie tun, und den Unterschied zwischen Wert- und Referenztypen zu verstehen, als über die Leistung nachzudenken. Die Nummer-Eins-Regel lautet: Verwenden Sie die Methode, die für Ihren Code besser geeignet ist .

Bei großen Werttypen (mehr als 64 Bit) übergeben Sie als Referenz, es sei denn, ein Vorteil beim Übergeben von Werten (z. B. einfacherer Code, "es macht einfach Sinn" oder Schnittstellenkonsistenz).

Bei kleineren Werttypen hat der Übergabemechanismus keinen großen Einfluss auf die Leistung, und es ist ohnehin schwer vorhersagbar, welche Methode schneller ist, da sie von der Objektgröße, der Verwendung des Objekts durch den Aufrufer und den Anrufer und sogar vom Cache-Aspekt abhängt . Machen Sie einfach das, was für Ihren Code sinnvoll ist.

2
Artelius

ByVal erstellt eine Kopie der Variablen, während ByRef einen Zeiger übergibt. Ich würde daher sagen, dass ByVal langsamer ist (aufgrund der Zeit, die das Kopieren erfordert) und mehr Speicher benötigt.

1
Colin

Meine Neugierde war es, die verschiedenen Verhaltensweisen zu überprüfen, je nach Objekt und Speicherbedarf

Das Ergebnis scheint demostrat zu sein, dass ByVal immer gewinnt. Die Ressource hängt davon ab, ob Speicher gesammelt wird oder weniger (nur 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
0
Fabio Guerrazzi