Производительность стоит использовать ref вместо возврата тех же типов? - PullRequest
12 голосов
/ 13 ноября 2011

Привет, это то, что действительно беспокоит меня, и я надеюсь, что у кого-то есть ответ для меня.Я читал о refout) и пытаюсь выяснить, не замедляю ли я свой код, используя ref s.Обычно я заменяю что-то вроде:

int AddToInt(int original, int add){ return original+add; }

на

void AddToInt(ref int original, int add){ original+=add; } // 1st parameter gets the result

, потому что на мой взгляд этот

AddToInt(ref _value, _add);

легче читать и кодировать, чем этот

_value = AddToInt(_value, _add);

Я точно знаю, что я делаю с кодом, используя ref, в отличие от возврата значения.Тем не менее, производительность - это то, к чему я отношусь серьезно, и, очевидно, разыменование и очистка намного медленнее, когда вы используете ссылки.

Что я хотел бы знать, это почему в каждом сообщении, которое я там читалочень мало мест, где вы обычно проходите ref (я знаю, что примеры надуманы, но я надеюсь, что вы поняли), когда мне кажется, что пример ref меньше, чище и точнее.

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

Ответы [ 4 ]

11 голосов
/ 13 ноября 2011

Основное время использования «ref» в той же форме, что и производительность, - это обсуждение некоторых весьма нетипичных случаев, например, в сценариях XNA, где игровые «объекты» довольно часто представлены структурами, а не классами, чтобы избежать проблем с GC (который оказывает непропорциональное влияние на XNA). Это становится полезным для:

  • предотвращает многократное копирование слишком большой структуры в стеке
  • предотвращение потери данных из-за изменения структурной копии (структуры XNA обычно изменчивы, в отличие от обычной практики)
  • позволяет передавать структуру в массиве напрямую , а не копировать ее обратно и обратно в

Во всех других случаях «ref» чаще ассоциируется с дополнительным побочным эффектом, который нелегко выражается в возвращаемом значении (например, см. Monitor.TryEnter).

Если у вас нет сценария, подобного сценарию XNA / struct, и нет неудобного побочного эффекта, просто используйте возвращаемое значение. Помимо того, что он более типичен (что само по себе имеет значение), он может также включать передачу меньшего количества данных (и int меньше, чем, например, ссылка на x64), и может потребовать меньше разыменования.

Наконец, возвратный подход более универсален; Вы не всегда хотите обновить источник. Контраст:

// want to accumulate, no ref
x = Add(x, 5);

// want to accumulate, ref
Add(ref x, 5);

// no accumulate, no ref
y = Add(x, 5);

// no accumulate, ref
y = x;
Add(ref y, x);

Я думаю, что последнее наименее понятно (с другим «ref», за которым он стоит), а использование ref еще менее понятно в языках, где оно не явное (например, VB).

5 голосов
/ 26 ноября 2013

Основная цель использования ключевого слова ref - показать, что значение переменной может быть изменено функцией, в которую она передается.Когда вы передаете переменную по значению, обновления внутри функции не влияют на исходную копию.

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

public void Quaternion.GetRollPitchYaw(ref double roll, ref double pitch, ref double yaw){
    roll = something;
    pitch = something;
    yaw = something;
}

Это довольно фундаментальный шаблон в языках, где использование указателей неограниченно.В c / c ++ вы часто видите примитивы, передаваемые по значению с классами и массивами в качестве указателей.C # делает прямо противоположное, поэтому 'ref' удобен в ситуациях, подобных описанным выше.

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

Иногда ref работает немного быстрее, когда используется в c # таким образом, но недостаточно для его использования в качестве оправдания производительности.

Вот что я получил на 7-летней машине, используя приведенный ниже код, передавая и обновляя строку 100 КБ по ref и по значению.

Вывод:

итераций: 10000000 по бэр: 165 мсек: 417 мс

private void m_btnTest_Click(object sender, EventArgs e) {

    Stopwatch sw = new Stopwatch();

    string s = "";
    string value = new string ('x', 100000);    // 100k string
    int iterations = 10000000;

    //-----------------------------------------------------
    // Update by ref
    //-----------------------------------------------------
    sw.Start();
    for (var n = 0; n < iterations; n++) {
        SetStringValue(ref s, ref value);
    }
    sw.Stop();
    long proc1 = sw.ElapsedMilliseconds;

    sw.Reset();

    //-----------------------------------------------------
    // Update by value
    //-----------------------------------------------------
    sw.Start();
    for (var n = 0; n < iterations; n++) {
        s = SetStringValue(s, value);
    }
    sw.Stop();
    long proc2 = sw.ElapsedMilliseconds;

    //-----------------------------------------------------
    Console.WriteLine("iterations: {0} \nbyref: {1}ms \nbyval: {2}ms", iterations, proc1, proc2);
}

public string SetStringValue(string input, string value) {
    input = value;
    return input;
}

public void SetStringValue(ref string input, ref string value) {
    input = value;
}
3 голосов
/ 13 ноября 2011

Я должен согласиться с Ондреем здесь. С стилистической точки зрения, если вы начнете передавать все с ref, вы в конечном итоге будете работать с разработчиками, которые захотят задушить вас за разработку такого API!

Просто верните вещи из метода, не возвращайте 100% ваших методов void. То, что вы делаете, приведет к очень грязному коду и может запутать других разработчиков, которые в конечном итоге будут работать над вашим кодом. Здесь вам понравится ясность в отношении производительности, так как в любом случае вы не добьетесь больших успехов в оптимизации.

проверьте это сообщение: C # 'ключевое слово' ref ', производительность

и эта статья от Джона Скита: http://www.yoda.arachsys.com/csharp/parameters.html

1 голос
/ 13 ноября 2011

Во-первых, не не беспокойтесь о том, будет ли использование ref медленнее или быстрее.Это преждевременная оптимизация.В 99,9999% случаев вы не столкнетесь с ситуацией, которая может привести к снижению производительности.

Во-вторых, возвращение результата вычисления в качестве возвращаемого значения вместо использования ref предпочтительнее, поскольку«функциональная» природа С-подобных языков.Это приводит к лучшей цепочке операторов / звонков.

...