Что быстрее? ByVal или ByRef? - PullRequest
35 голосов
/ 03 января 2009

В VB.NET, который быстрее использовать для аргументов метода, ByVal или ByRef?

Кроме того, что потребляет больше ресурсов во время выполнения (RAM)?


Я прочитал этот вопрос , но ответы не применимы или недостаточно конкретны.

Ответы [ 7 ]

122 голосов
/ 03 января 2009

Аргументы Byval и ByRef должны использоваться на основе требований и знаний о том, как они работают не на скорости.

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

В ответ на комментарий Слау -

Что потребляет больше ресурсов во время выполнения?

Параметры передаются в стек. Стек очень быстрый, потому что его выделение памяти - просто приращение указателя, чтобы зарезервировать новый «кадр» или «запись выделения». Большинство параметров .NET не превышают размер машинного регистра настолько мало, если для передачи параметров используется какое-либо «стековое» пространство. На самом деле базовые типы и указатели размещаются в стеке. Размер стека в .NET ограничен 1 МБ. Это должно дать вам представление о том, как мало ресурсов потребляется при передаче параметров.

Вам может понравиться эта серия статей:

Повышение производительности за счет распределения стека (.NET Memory Management: часть 2)

Что быстрее? ByVal или ByRef.

Трудно в лучшем случае точно и точно измерить - в зависимости от контекста вашего измерения, но тест, который я написал, вызывая метод 100 миллионов раз, дал следующее:

  • Тип ссылки: пройдено по ссылке: 420 мс
  • Тип ссылки - пройдено ByVal: 382 мс
  • Тип значения - Passed ByRef: 421 мс
  • Тип значения - пройдено ByVal: 416 мс
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

Комментирование переменной и присваивания в каждом методе -

  • Тип ссылки - Пройдено по ссылке: 389 мс
  • Тип ссылки - пройдено ByVal: 349 мс
  • Тип значения - Passed ByRef: 416 мс
  • Тип значения - пройдено ByVal: 385 мс

Можно сделать вывод, что передача ссылочных типов (строк, классов) ByVal сэкономит некоторое время. Можно также сказать, что передача значений типа (целое число, байт) - ByVal сэкономит некоторое время.

Опять время незначительно в великой схеме вещей. Что более важно, это правильно использовать ByVal и ByRef и понимать, что происходит «за кулисами». Алгоритмы, реализованные в ваших подпрограммах, наверняка будут влиять на время выполнения вашей программы во много раз больше.

30 голосов
/ 03 января 2009

Если вы используете тип значения очень большого размера (например, Guid довольно большой), передача параметра по ссылке может быть немного быстрее. В других случаях может быть больше копирования и т. Д. При передаче по ссылке, а не по значению - например, если у вас есть параметр байта, то один байт явно меньше четырех или восьми байтов, которые указатель возьмет, если вы передадите его по ссылке.

На практике вы почти никогда не должны беспокоиться об этом. Напишите максимально читаемый код, который почти всегда означает передачу параметров по значению, а не по ссылке. Я использую ByRef очень редко.

Если вы хотите улучшить производительность и думаете, что ByRef поможет вам, , пожалуйста, тщательно сравните его (в вашей конкретной ситуации), прежде чем совершать его.

РЕДАКТИРОВАТЬ: я отмечаю в комментариях к другому (ранее принятому, теперь удаленному) ответу, что существует большое недопонимание того, что означает ByRef против ByVal, когда речь идет о типах значений. У меня есть статья о передаче параметров , которая за последние годы стала популярной - она ​​используется в терминологии C #, но те же понятия применимы и к VB.NET.

11 голосов
/ 03 января 2009

Это зависит. Если вы передаете объект, он уже передает указатель. Вот почему, если вы передаете ArrayList (например) и ваш метод добавляет что-то в ArrayList, то вызывающий код также имеет тот же объект в свой ArrayList, который был передан, потому что это тот же ArrayList. Единственный раз, когда он не передает указатель, это когда вы передаете в функцию переменную с внутренним типом данных, например, int или double. В этот момент он создает копию. Однако размер данных этих объектов настолько мал, что вряд ли что-то изменится с точки зрения использования памяти или скорости выполнения.

5 голосов
/ 03 января 2009

Если вы передаете ссылочный тип, ByRef работает медленнее.

Это потому, что передается указатель на указатель. Любой доступ к полям объекта требует разыменования дополнительного указателя, для завершения которого потребуется несколько дополнительных тактов.

Если вы передаете тип значения, тогда byref может быть быстрее, если структура имеет много членов, потому что она передает только один указатель, а не копирует значения в стек. С точки зрения доступа к элементам, byref будет медленнее, потому что он должен сделать дополнительную разыменование указателя (sp-> pValueType-> member vs sp-> member).

Большую часть времени в VB вам не нужно беспокоиться об этом.

В .NET редко встречаются типы значений с большим количеством членов. Они обычно маленькие. В этом случае передача типа значения не отличается от передачи нескольких аргументов в процедуру. Например, если бы у вас был код, который передавал объект Point по значению, его perf был бы таким же, как метод, принимающий значения X и Y в качестве параметров. Видение DoSomething (x как целое число, y как целое число), вероятно, не вызовет проблем с перфорированием. На самом деле, вы, вероятно, никогда бы не подумали об этом дважды.

Если вы сами определяете типы больших значений, то вам, вероятно, следует пересмотреть вопрос о преобразовании их в ссылочные типы.

Единственным другим отличием является увеличение числа косвенных указателей, необходимых для выполнения кода. Редко когда вам нужно оптимизировать на этом уровне. В большинстве случаев существуют либо алгоритмические проблемы, которые вы можете решить, либо узкое место перфорации связано с вводом-выводом, например, ожидание базы данных или запись в файл, и в этом случае устранение косвенных указателей не очень вам поможет.

Таким образом, вместо того, чтобы сосредоточиться на том, является ли Byval или Byref быстрее, я бы порекомендовал вам сосредоточиться на том, что дает вам семантику, которая вам нужна. В общем случае, рекомендуется использовать byval, если вам не нужен byref. Это значительно облегчает понимание программы.

2 голосов
/ 03 января 2009

Хотя я не знаю много о внутренностях .NET, я расскажу, что я знаю о скомпилированных языках. Это не относится к ссылочным типам и может быть не совсем точным в отношении типов значений. Если вы не знаете разницу между типами значений и ссылочными типами, вам не следует это читать. Я предполагаю, что 32-битный x86 (с 32-битными указателями).

  • При передаче значений, меньших 32-битных, в стеке по-прежнему используется 32-битный объект. Часть этого объекта будет «неиспользована» или «заполнена». Передача таких значений не использует меньше памяти, чем передача 32-битных значений.
  • Передача значений размером более 32 бит будет занимать больше места в стеке, чем указатель, и, возможно, больше времени копирования.
  • Если объект передается по значению, вызываемый объект может извлечь объект из стека. Если объект передается по ссылке, вызываемый объект должен сначала извлечь адрес объекта из стека, а затем получить значение объекта из другого места. По значению означает на одну выборку меньше, верно? Ну, на самом деле выборка должна быть выполнена вызывающей стороной - однако вызывающая сторона, возможно, уже должна была получить выборку по разным причинам, и в этом случае выборка сохраняется.
  • Очевидно, что любые изменения, внесенные в значение по ссылке, должны быть сохранены обратно в ОЗУ, тогда как параметр по значению может быть отброшен.
  • Лучше передавать по значению, чем передавать по ссылке, только чтобы скопировать параметр в локальную переменную и не прикасаться к нему снова.

Приговор:

Гораздо важнее понять, что на самом деле делают для вас ByVal и ByRef, и понять разницу между значениями и ссылочными типами, чем думать о производительности. Правило номер один - использовать тот метод, который больше подходит вашему коду .

Для типов больших значений (более 64 бит) передайте по ссылке, если нет преимущества перед передачей по значению (например, более простой код, «это просто имеет смысл» или согласованность интерфейса).

Для меньших типов значений механизм передачи не имеет большого значения для производительности, и в любом случае сложно предсказать, какой метод будет быстрее, поскольку он зависит от размера объекта, того, как вызывающий объект и вызываемый объект используют объект, и даже кеш соображений. Просто делайте все, что имеет смысл для вашего кода.

1 голос
/ 03 января 2009

ByVal создает копию переменной, тогда как ByRef передает указатель. Поэтому я бы сказал, что ByVal медленнее (из-за времени, необходимого для копирования) и использует больше памяти.

0 голосов
/ 15 ноября 2014

Мне было любопытно проверить различные варианты поведения в зависимости от объекта и использования памяти

Результат кажется убедительным в том, что 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
...