Domanda

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

Inoltre, quale consuma più risorse in fase di runtime (RAM)?


Ho letto questa domanda , ma le risposte non sono applicabile o abbastanza specifico.

È stato utile?

Soluzione

Gli argomenti Byval e ByRef dovrebbero essere usati in base ai requisiti e alla conoscenza di come funzionano non sulla velocità.

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

In risposta a un commento di Slough -

Quale consuma più risorse in fase di esecuzione?

I parametri vengono passati nello stack. Lo stack è molto veloce, perché la sua allocazione di memoria è semplicemente un incremento del puntatore per riservare un nuovo "frame" oppure "record di allocazione". La maggior parte dei parametri .NET non supera le dimensioni di un registro macchina così piccolo se non esiste uno "stack" lo spazio viene utilizzato per passare i parametri. In effetti, tipi e puntatori di base sono entrambi allocati nello stack. Le dimensioni dello stack in .NET sono limitate a 1 & nbsp; MB. Questo dovrebbe darti un'idea di quante poche risorse vengono consumate dal passaggio dei parametri.

Puoi trovare interessante questa serie di articoli:

Miglioramento delle prestazioni tramite allocazione dello stack (gestione della memoria .NET: parte 2)

Qual è più veloce? ByVal o ByRef.

Nella migliore delle ipotesi è difficile misurare con precisione e fata - a seconda del contesto della misurazione, ma un benchmark che ho scritto chiamando un metodo 100 milioni di volte ha prodotto quanto segue:

  • Tipo di riferimento - Passato da Rif: 420 ms
  • Tipo di riferimento - Passato da ByVal: 382 ms
  • Tipo di valore - Passato da Rif: 421 ms
  • Tipo valore - Passato da 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 - Passato da Rif: 389 ms
  • Tipo di riferimento - Passato da ByVal: 349 ms
  • Tipo valore - Passato da Rif: 416 ms
  • Tipo valore - Passato da ByVal: 385 ms

Si potrebbe concludere che il passaggio di tipi di riferimento (stringhe, classi) ByVal farà risparmiare un po 'di tempo. Potresti anche dire che il passaggio di tipi di valore (intero, byte) - ByVal farà risparmiare un po 'di tempo.

Ancora una volta il tempo è trascurabile nel grande schema delle cose. La cosa più importante è usare correttamente ByVal e ByRef e capire cosa sta succedendo "dietro le quinte". Gli algoritmi implementati nelle tue routine influenzeranno sicuramente il runtime del tuo programma molte volte di più.

Altri suggerimenti

Se si utilizza un tipo di valore molto grande (Guid è piuttosto grande, ad esempio) potrebbe essere leggermente più veloce passare un parametro per riferimento. In altri casi, ci può essere più copia ecc. Quando si passa per riferimento piuttosto che per valore - ad esempio, se si dispone di un parametro byte, un byte è chiaramente inferiore ai quattro o otto byte che il puntatore prenderebbe se lo passassi per riferimento.

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

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

EDIT: noto nei commenti a un'altra risposta (precedentemente accettata, ora eliminata) che ci sono molti fraintendimenti su cosa significhi ByRef vs ByVal quando si tratta di tipi di valore. Ho un un articolo sul passaggio dei parametri che si è rivelato popolare negli anni - è in C # terminologia, ma gli stessi concetti si applicano a VB.NET.

Dipende. Se stai passando un oggetto, sta già passando un puntatore. Ecco perché se passi un ArrayList (per esempio) e il tuo metodo aggiunge qualcosa all'ArrayList, allora anche il codice chiamante ha lo stesso oggetto 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 double, nella funzione. A quel punto, crea una copia. Tuttavia, la dimensione dei dati di questi oggetti è così piccola, che difficilmente farebbe differenza in termini di utilizzo della memoria o velocità di esecuzione.

Se si passa a un tipo di riferimento, ByRef è più lento.

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

Se si passa 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 nello stack. In termini di accesso ai membri, byref sarà più lento perché deve eseguire una ulteriore dereference puntatore (sp- > pValueType- > member vs sp- > member).

Il più 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, passare un tipo di valore non è diverso dal passare più argomenti a una procedura. Ad esempio, se avessi il codice che ha passato un oggetto 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 intero) probabilmente non causerebbe preoccupazioni perf. In effetti, probabilmente non ci penseresti mai due volte.

Se si stanno definendo tipi di valore di grandi dimensioni, è consigliabile riconsiderarli trasformandoli in tipi di riferimento.

L'unica altra differenza è l'aumento del numero di riferimenti indiretti del puntatore richiesti per eseguire il codice. È raro che tu abbia mai bisogno di ottimizzare a quel livello. Il più delle volte, ci sono o problemi algoritmici che puoi affrontare o il tuo collo di bottiglia perfetto è correlato all'IO, come aspettare un database o scrivere su un file, nel qual caso l'eliminazione delle indirette del puntatore non ti aiuterà molto.

Quindi, invece di concentrarti su più byte o byref è più veloce, consiglierei che dovresti davvero 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 bisogno di byref. Rende il programma molto più facile da capire.

Anche se non so molto sugli interni di .NET, parlerò di ciò che so sui linguaggi compilati. Questo non si applica ai tipi di riferimento e potrebbe non essere del tutto preciso sui tipi di valore. Se non conosci la differenza tra tipi di valore e tipi di riferimento, non dovresti leggere questo. Presumo x86 a 32 bit (con puntatori a 32 bit).

  • Il passaggio di valori inferiori a 32 bit utilizza ancora un oggetto a 32 bit nello stack. Parte di questo oggetto sarà "inutilizzata" o "riempimento". 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 di un puntatore e probabilmente più tempo di copia.
  • Se un oggetto viene passato per valore, il chiamato può recuperare l'oggetto dallo stack. Se un oggetto viene passato per riferimento, il chiamante deve prima recuperare l'indirizzo dell'oggetto dallo stack, quindi recuperare il valore dell'oggetto da un'altra parte. Per valore significa un recupero in meno, giusto? Bene, in realtà il recupero deve essere eseguito dal chiamante, tuttavia il chiamante potrebbe aver già dovuto recuperare per diversi motivi, nel qual caso un recupero viene salvato.
  • Ovviamente qualsiasi modifica apportata a un valore per riferimento deve essere salvata nella RAM, mentre un parametro per 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 realmente ByVal e ByRef e capire la differenza tra valore e tipi di riferimento, piuttosto che pensare alle prestazioni. La regola numero uno è utilizzare qualunque metodo sia più appropriato al tuo codice .

Per tipi di valore di grandi dimensioni (più di 64 bit), passa per riferimento a meno che non vi sia un vantaggio nel passare per valore (come un codice più semplice, "ha senso" o coerenza dell'interfaccia).

Per 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'oggetto, da come il chiamante e il chiamante usano l'oggetto, e anche considerazioni sulla cache. Fai semplicemente quello 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 impiegato per la copia) e utilizza più memoria.

La mia curiosità era quella di verificare i diversi comportamenti a seconda dell'uso di oggetti e memoria

Il risultato sembra dimostrare che ByVal vince sempre, la risorsa dipende se si 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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top