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

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

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


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


person Robin Rodricks    schedule 02.01.2009    source источник
comment
Другой ответ может быть таким: если выбор ByVal / ByRef становится критичным для скорости приложения, возможно, пришло время делать меньше вызовов функций ...   -  person GameAlchemist    schedule 07.09.2011


Ответы (7)


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

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

В ответ на комментарий Slough -

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

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

Возможно, вам будет интересна эта серия статей:

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

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

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

  • Тип ссылки - пройдено Ссылка: 420 мс
  • Тип ссылки - прошло значение: 382 мс
  • Тип значения - Передано Ссылка: 421 мс
  • Тип значения - пройдено Значение: 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 мс
  • Тип ссылки - пройдено Значение: 349 мс
  • Тип значения - Передано Ссылка: 416 мс
  • Тип значения - прошло значение: 385 мс

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

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

person user50612    schedule 02.01.2009
comment
К сожалению, сегодня голосов не осталось - но в остальном от меня большой +1. - person Jon Skeet; 03.01.2009
comment
Здесь, здесь. Если вы хотите повысить производительность, изменив это, вы, вероятно, не знаете, что, черт возьми, делаете. - person ctacke; 03.01.2009
comment
ответ правильный, но вопрос был не в этом? Почему мы предполагаем, что тот, кто вводит вопросы, все равно не знает об этом? - person dr. evil; 03.01.2009
comment
Время, необходимое для передачи типа значения ByVal, будет варьироваться в зависимости от размера рассматриваемого типа значения. Времени на прохождение ByRef не будет. Если типы значений большие, передача ByRef может быть намного быстрее. Обратите внимание, что даже типы с очень большими значениями (тысячи байтов) могут быть очень производительными, если можно избежать передачи их по значению или ненужного копирования. - person supercat; 28.06.2012
comment
При большом массиве Byte() ByVal быстрее, чем ByRef, но также незначительно. Я изменил этот тест, чтобы использовать Byte() вместо String, и запустил его с массивом байтов 6 МБ. ByRef был 331 мс, а ByVal был 231 мс. Это ничтожно мало: разница в 0,000001 мс на вызов. - person GreenRaccoon23; 10.12.2020

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

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

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

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

person Jon Skeet    schedule 02.01.2009

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

person Kibbee    schedule 02.01.2009

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

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

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

В большинстве случаев в VB вам не о чем беспокоиться.

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

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

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

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

person Scott Wisniewski    schedule 02.01.2009

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

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

Вердикт:

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

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

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

person Artelius    schedule 03.01.2009

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

person Paul    schedule 02.01.2009
comment
Хотя это всего лишь догадки. Рассмотрим байтовый параметр - а затем рассмотрим размер указателя ... - person Jon Skeet; 03.01.2009
comment
Фактически рассмотрите любой тип, размер которого меньше размера указателя. (Короткое, например) - person pyon; 03.01.2009
comment
Теперь представьте строку, содержащую содержимое большого текстового файла. Без догадок, ByRef быстрее. Я предполагаю, что это не имеет значения, когда размеры маленькие. - person bruceatk; 03.01.2009

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

Результат, кажется, демонстрирует, что 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
person Fabio Guerrazzi    schedule 15.11.2014
comment
Вы передаете очень маленькую структуру по значению, очевидно, что по ссылке в этом случае будет медленнее. Не могли бы вы также добавить GUID к своей структуре и классу и попробовать еще раз? - person Ziriax; 04.01.2017