Quale è più veloce? ByVal o ByRef?

In VB.NET, che è più veloce da usare per gli argomenti del metodo, ByVal o ByRef ?

Inoltre, che consuma più risorse in fase di esecuzione (RAM)?


Ho letto questa domanda , ma le risposte non sono applicabili o abbastanza specifiche.

Gli argomenti Byval e ByRef dovrebbero essere utilizzati in base ai requisiti e alle conoscenze su come funzionano non in base alla velocità.

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

In risposta a un commento di Slough –

Che consuma più risorse in fase di runtime?

I parametri vengono passati in pila. Lo stack è molto veloce, perché la sua allocazione di memoria è semplicemente un incremento puntatore per riservare un nuovo “frame” o “record di allocazione”. La maggior parte dei parametri .NET non supera le dimensioni di un registro di macchina, quindi è sufficiente utilizzare uno spazio “stack” per passare i parametri. In effetti, i tipi base e i puntatori sono entrambi assegnati in pila. La dimensione dello stack in .NET è limitata a 1 MB. Questo dovrebbe darti un’idea di quanto poche risorse siano consumate dal passaggio dei parametri.

Potresti trovare questa serie di articoli interessanti:

Miglioramento delle prestazioni attraverso l’allocazione dello stack (Gestione della memoria .NET: parte 2)

Quale è più veloce? ByVal o ByRef.

Nella maggior parte dei casi è difficile misurare con precisione e fata – a seconda del contesto della tua misurazione, ma un benchmark che ho scritto chiamando un metodo 100 milioni di volte ha prodotto il seguente:

  • Tipo di riferimento – Passed ByRef: 420 ms
  • Tipo di riferimento – superato ByVal: 382 ms
  • Valore Tipo – Passato ByRef: 421 ms
  • Valore Tipo – Passato 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 

Commentando la variabile e l’assegnazione in ciascun metodo:

  • Tipo di riferimento – Passed ByRef: 389 ms
  • Tipo di riferimento – superato ByVal: 349 ms
  • Tipo di valore – Passed ByRef: 416 ms
  • Valore Tipo – Passato ByVal: 385 ms

Si potrebbe concludere che i tipi di riferimento di passaggio (stringhe, classi) ByVal salveranno un po ‘di tempo. Si potrebbe anche dire che i tipi di valori che passano (intero, byte) – ByVal salveranno un po ‘di tempo.

Di nuovo il tempo è trascurabile nel grande schema delle cose. La cosa più importante è usare ByVal e ByRef correttamente e capire cosa succede dietro le quinte. ” Gli algoritmi implementati nelle tue routine avranno sicuramente più effetto sul runtime del tuo programma molte volte di più.

Se stai usando un tipo di valore molto grande (Guid è piuttosto grande, per esempio) potrebbe essere leggermente più veloce passare un parametro per riferimento. In altri casi, potrebbe esserci più copia ecc. Quando passi per riferimento che per valore – ad esempio, se hai un parametro byte, allora un byte è chiaramente inferiore ai quattro o otto byte che il puntatore assumerebbe se tu passato per riferimento.

In pratica, non dovresti quasi mai preoccuparti di questo. Scrivi il codice più leggibile ansible, che quasi sempre significa passare i parametri per valore anziché per riferimento. Io uso ByRef molto raramente.

Se vuoi migliorare le prestazioni e pensare che ByRef ti aiuterà, ti preghiamo di valutarlo attentamente (nella tua esatta situazione) prima di impegnarti.

EDIT: Osservo nei commenti ad un’altra risposta (precedentemente accettata, ora cancellata) che c’è un grande malinteso su ciò che ByRef vs ByVal significa quando si tratta di tipi di valore. Ho un articolo sul passaggio dei parametri che si è dimostrato popolare nel corso degli anni: è nella terminologia C #, ma gli stessi concetti si applicano a VB.NET.

Dipende. Se stai passando un object, sta già passando un puntatore. Ecco perché se passi un ArrayList (per esempio) e il tuo metodo aggiunge qualcosa a ArrayList, allora il codice chiamante ha anche lo stesso object nel suo ArrayList, che è stato passato, perché è lo stesso ArrayList. L’unica volta che non passa un puntatore, è quando si passa una variabile con un tipo di dati intrinseco, come un int o un doppio, nella funzione. A quel punto, crea una copia. Tuttavia, la dimensione dei dati di questi oggetti è così piccola che difficilmente farebbe la differenza in entrambi i casi, in termini di utilizzo della memoria o velocità di esecuzione.

Se stai passando un tipo di riferimento, ByRef è più lento.

Questo perché ciò che viene passato è un puntatore a un puntatore. Qualsiasi accesso ai campi sull’object richiede il dereferenziamento di un puntatore aggiuntivo, che richiederà alcuni cicli di clock aggiuntivi.

Se stai passando un tipo di valore, allora byref potrebbe essere più veloce se la struttura ha molti membri, perché passa solo un singolo puntatore invece di copiare i valori sullo stack. In termini di accesso ai membri, byref sarà più lento perché ha bisogno di fare un dereferenziatore extra (sp-> pValueType-> membro vs sp-> membro).

La maggior parte delle volte in VB non dovresti preoccuparti di questo.

In .NET è raro avere tipi di valore con un numero elevato di membri. Di solito sono piccoli. In tal caso, il passaggio di un tipo di valore non è diverso dal passaggio di più argomenti a una procedura. Ad esempio, se avessi il codice passato in un object Point in base al valore, perf sarebbe lo stesso di un metodo che ha preso i valori X e Y come parametri. Vedere DoSomething (x come intero, y come numero intero) probabilmente non causerebbe problemi di perfusione. In effetti, probabilmente non ci penseresti mai due volte.

Se stai definendo tipi di valore elevato come te, dovresti probabilmente riconsiderare la loro trasformazione in tipi di riferimento.

L’unica altra differenza è l’aumento del numero di puntatori indiretti richiesti per eseguire il codice. È raro che tu abbia sempre bisogno di ottimizzare a quel livello. La maggior parte delle volte, ci sono problemi algoritmici che puoi affrontare, o il tuo collo di bottiglia perf è legato all’IO, come aspettare un database o scrivere su un file, nel qual caso eliminare il puntatore indiretto non ti aiuterà molto.

Quindi, invece di focalizzarci su wheter byval o byref è più veloce, ti consiglio di concentrarti su ciò che ti dà la semantica di cui hai bisogno. In generale, è una buona idea usare byval a meno che tu non abbia specificamente bisogno di byref. Rende il programma molto più facile da capire.

Mentre non conosco molto i componenti interni di .NET, discuterò di ciò che so sui linguaggi compilati. Questo non si applica ai tipi di riferimento e potrebbe non essere completamente accurato sui tipi di valore. Se non conosci la differenza tra tipi di valore e tipi di riferimento, non dovresti leggerlo. Assumerò x86 a 32 bit (con puntatori a 32 bit).

  • Il passaggio di valori inferiori a 32 bit utilizza ancora un object a 32 bit nello stack. Parte di questo object sarà “inutilizzato” o “imbottito”. Il passaggio di tali valori non utilizza meno memoria rispetto al passaggio di valori a 32 bit.
  • Il passaggio di valori superiori a 32 bit utilizzerà più spazio di stack rispetto a un puntatore e probabilmente più tempo di copia.
  • Se un object viene passato per valore, il callee può recuperare l’object dallo stack. Se un object viene passato per riferimento, il callee deve prima recuperare l’indirizzo dell’object dallo stack e quindi recuperare il valore dell’object da altrove. Per valore si intende una lettura in meno, giusto? Bene, in realtà il recupero deve essere fatto dal chiamante – tuttavia il chiamante potrebbe aver già dovuto recuperare per ragioni diverse, nel qual caso viene recuperato un recupero.
  • Ovviamente tutte le modifiche apportate a un valore di riferimento devono essere salvate nella RAM, mentre un parametro di valore può essere scartato.
  • È meglio passare per valore, piuttosto che passare per riferimento solo per copiare il parametro in una variabile locale e non toccarlo di nuovo.

Il verdetto:

È molto più importante capire cosa fanno effettivamente ByVal e ByRef per te, e capire la differenza tra i tipi di valore e di riferimento, piuttosto che pensare alle prestazioni. La regola numero uno consiste nell’utilizzare qualsiasi metodo sia più appropriato per il codice .

Per i tipi di grande valore (più di 64 bit), passa per riferimento a meno che non vi sia un vantaggio per il passaggio per valore (come il codice più semplice, “ha senso” o coerenza dell’interfaccia).

Per i tipi di valore più piccoli, il meccanismo di passaggio non fa molta differenza per le prestazioni, e comunque è difficile prevedere quale metodo sarà più veloce, poiché dipende dalla dimensione dell’object, dal modo in cui il chiamante e il chiamato utilizzano l’object e anche considerazioni sulla cache . Fai solo ciò che ha senso per il tuo codice.

ByVal crea una copia della variabile, mentre ByRef passa un puntatore. Direi quindi che ByVal è più lento (a causa del tempo necessario per copiare) e utilizza più memoria.

La mia curiosità era di verificare i diversi comportamenti a seconda degli oggetti e degli usi della memoria

Il risultato sembra dimostrare che ByVal vince sempre, la risorsa dipende se raccoglie memoria o meno (solo 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