web-dev-qa-db-fra.com

Lequel est plus vite? ByVal ou ByRef?

Dans VB.NET, qui est plus rapide à utiliser pour les arguments de méthode, ByVal ou ByRef?

Aussi, qui consomme plus de ressources au moment de l'exécution (RAM)?


J'ai lu cette question , mais les réponses ne sont pas applicables ou suffisamment spécifiques.

35
Robinicks

Les arguments Byval et ByRef doivent être utilisés en fonction des besoins et de la connaissance de leur fonctionnement non sur la vitesse.

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

En réponse à un commentaire de Slough -

Qu'est-ce qui consomme plus de ressources au moment de l'exécution?

Les paramètres sont passés sur la pile. La pile est très rapide, car son allocation de mémoire est simplement un incrément de pointeur permettant de réserver un nouvel "enregistrement" ou "enregistrement d'allocation". La plupart des paramètres .NET ne dépassent pas la taille d'un registre de machine, si peu d'espace est utilisé pour transmettre les paramètres. En fait, les types de base et les pointeurs sont tous deux alloués sur la pile. La taille de la pile dans .NET est limitée à 1 Mo. Cela devrait vous donner une idée du peu de ressources consommées par le passage de paramètres.

Vous pouvez trouver cette série d'articles intéressants:

Amélioration des performances grâce à l'allocation de pile (Gestion de la mémoire .NET: Partie 2) 

Lequel est le plus rapide? ByVal ou ByRef.

Au mieux, il est difficile de mesurer avec précision et fée - en fonction du contexte de votre mesure, mais un critère que j'ai écrit, appelant une méthode 100 millions de fois, a donné les résultats suivants:

  • Type de référence - passé par le référentiel: 420 ms
  • Type de référence - Validé par: 382 ms
  • Type de valeur - Passé parRef: 421 ms
  • Type de valeur - Validé par: 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

Mise en commentaire de la variable et affectation dans chaque méthode -

  • Type de référence - Réussi: 389 ms
  • Type de référence - Validé par: 349 ms
  • Type de valeur - Passé parRef: 416 ms
  • Type de valeur - passé par la valeur: 385 ms

On pourrait en conclure que le passage de types de références (chaînes, classes) à ByVal vous fera gagner du temps. Vous pourriez aussi dire que passer des types de valeur (entier, octet) - ByVal vous fera gagner du temps.

Encore une fois, le temps est négligeable dans le grand schéma des choses. Le plus important est d’utiliser correctement ByVal et ByRef et de comprendre ce qui se passe "en coulisse". Les algorithmes mis en œuvre dans vos routines affecteront sûrement beaucoup plus l'exécution de votre programme.

122
user50612

Si vous utilisez un type de valeur très grand (Guid est assez gros, par exemple), il peut être très légèrement plus rapide de passer un paramètre par référence. Dans d’autres cas, il peut y avoir more copier etc. lorsque vous passez par référence plutôt que par valeur - par exemple, si vous avez un paramètre d’octet, un octet est clairement inférieur aux quatre ou huit octets le pointeur prendrait si vous le passiez par référence.

En pratique, vous ne devriez presque jamais vous inquiéter de cela. Ecrivez le code le plus lisible possible, ce qui signifie presque toujours que vous transmettez des paramètres par valeur plutôt que par référence. J'utilise très rarement ByRef.

Si vous souhaitez améliorer les performances et pensez que ByRef peut vous aider, please testez-le avec précaution (dans votre situation exacte) avant de vous y engager.

EDIT: Je remarque dans les commentaires d'une autre réponse (précédemment acceptée, maintenant supprimée) qu'il y a beaucoup de malentendus sur ce que ByRef vs ByVal signifie quand il s'agit de types de valeur. J'ai un article sur le passage de paramètres qui a fait ses preuves au fil des ans - il est en terminologie C #, mais les mêmes concepts s'appliquent à VB.NET.

30
Jon Skeet

Ça dépend. Si vous passez un objet, il passe déjà un pointeur. C'est pourquoi, si vous transmettez une ArrayList (par exemple) et que votre méthode ajoute quelque chose à ArrayList, alors le code appelant contient également le même objet que ArrayList, car il s'agit du même ArrayList. La seule fois où il ne passe pas un pointeur, c'est lorsque vous passez une variable avec un type de données intrinsèque, comme un int ou un double, dans la fonction. À ce stade, il crée une copie. Cependant, la taille des données de ces objets est si petite que cela ne ferait guère de différence, que ce soit en termes d'utilisation de la mémoire ou de vitesse d'exécution.

11
Kibbee

Si vous passez un type de référence, ByRef est plus lent.

En effet, ce qui est transmis est un pointeur sur un pointeur. Tout accès aux champs de l'objet nécessite de déréférencer un pointeur supplémentaire, ce qui prend quelques cycles d'horloge supplémentaires.

Si vous transmettez un type de valeur, byref peut être plus rapide si la structure compte plusieurs membres, car elle ne transmet qu'un seul pointeur au lieu de copier les valeurs de la pile. En termes d'accès aux membres, byref sera plus lent car il doit effectuer une déréférence de pointeur supplémentaire (sp-> pValueType-> member vs sp-> membre).

La plupart du temps dans VB vous ne devriez pas vous inquiéter à ce sujet.

Dans .NET, il est rare d'avoir des types de valeur avec un grand nombre de membres. Ils sont généralement petits. Dans ce cas, la transmission d'un type de valeur n'est pas différente de la transmission de plusieurs arguments à une procédure. Par exemple, si vous aviez un code qui passait dans un objet Point par valeur, sa performance serait identique à une méthode qui prend les valeurs X et Y en tant que paramètres. Voir Quelque chose (x en entier, y en entier) ne poserait probablement pas de problème de performance. En fait, vous n'y penseriez probablement jamais à deux fois.

Si vous définissez vous-même des types de grande valeur, vous devriez probablement reconsidérer leur conversion en types de référence.

La seule autre différence est l'augmentation du nombre d'indexions de pointeur requises pour exécuter le code. Il est rare que vous ayez besoin d'optimiser à ce niveau. La plupart du temps, il existe des problèmes d'algorithmique que vous pouvez résoudre, ou votre goulot d'étranglement de perf est lié à IO, comme l'attente de une base de données ou l'écriture dans un fichier, auquel cas éliminer les indirections de pointeur ne vous aidera pas beaucoup.

Donc, au lieu de vous concentrer plus rapidement sur byval ou byref, je vous recommande de vous concentrer sur ce qui vous donne la sémantique dont vous avez besoin. En général, c'est une bonne idée d'utiliser byval sauf si vous avez spécifiquement besoin de byref. Cela rend le programme beaucoup plus facile à comprendre.

5
Scott Wisniewski

Bien que je ne connaisse pas grand chose au sujet des composants internes de .NET, je vais parler de ce que je sais des langages compilés. Ce ne s'applique pas aux types de référence et peut ne pas être complètement précis en ce qui concerne les types de valeur. Si vous ne connaissez pas la différence entre les types de valeur et les types de référence, vous ne devriez pas lire ceci. Je supposerai x86 32 bits (avec des pointeurs 32 bits).

  • Les valeurs inférieures à 32 bits utilisent toujours un objet 32 ​​bits de la pile. Une partie de cet objet sera "inutilisé" ou "padding". Passer de telles valeurs n'utilise pas moins de mémoire que de transmettre des valeurs 32 bits.
  • Le passage de valeurs supérieures à 32 bits utilisera plus d'espace de pile qu'un pointeur et probablement plus de temps de copie.
  • Si un objet est passé par valeur, l'appelé peut extraire l'objet de la pile. Si un objet est passé par référence, l'appelé doit d'abord extraire l'adresse de l'objet de la pile, puis extraire la valeur de l'objet ailleurs. Par valeur signifie une extraction de moins, non? En réalité, l'appelant doit effectuer l'extraction. Toutefois, l'appelant a peut-être déjà eu à extraire de l'eau pour des raisons différentes, auquel cas l'extraction est enregistrée.
  • Évidemment, toute modification apportée à une valeur par référence doit être sauvegardée dans la RAM, alors qu'un paramètre par valeur peut être ignoré.
  • Il est préférable de passer par valeur plutôt que de passer par référence uniquement pour copier le paramètre dans une variable locale et ne pas le toucher à nouveau.

Le verdict:

Il est beaucoup plus important de comprendre ce que ByVal et ByRef font réellement pour vous et de comprendre la différence entre les types valeur et référence, plutôt que de penser à la performance. La règle numéro un est de utiliser la méthode qui convient le mieux à votre code .

Pour les types de grande valeur (plus de 64 bits), transmettez par référence à moins qu'il ne soit avantageux de transmettre par valeur (par exemple, un code plus simple, "cela a du sens" ou la cohérence de l'interface).

Pour les types de valeur plus petits, le mécanisme de transmission n'a pas beaucoup d'incidence sur les performances. De toute façon, il est difficile de prévoir quelle méthode sera la plus rapide, car cela dépend de la taille de l'objet, de la manière dont l'appelant et l'appelé l'utilisent et même de considérations relatives au cache . Faites juste ce qui a du sens pour votre code.

2
Artelius

ByVal crée une copie de la variable, alors que ByRef transmet un pointeur. Je dirais donc que ByVal est plus lent (en raison du temps nécessaire pour copier) et utilise plus de mémoire.

1
Colin

Ma curiosité était de vérifier les différents comportements en fonction des usages des objets et de la mémoire

Le résultat semble démontrer que ByVal gagne toujours, la ressource dépend si collecter de la mémoire ou moins (4.5.1 uniquement)

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