使用ref而不是返回相同类型的性能成本?

时间:2011-11-13 10:37:39

标签: c# performance ref

嗨,这真是困扰我的事情,我希望有人能给我一个答案。我一直在阅读有关ref(和out)的内容,而我正试图弄清楚我是否正在使用ref减慢代码速度。通常我会替换像:

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对代码做什么,而不是返回一个值。但是,性能是我认真对待的,当您使用refs时,显然解除引用和清理会慢得多。

我想知道的是为什么我读过的每篇文章都说你通常会传递ref的地方很少(我知道这些例子都是人为的,但我希望你得到了这个想法),在我看来,ref例子更小,更清洁,更准确。

我也很想知道为什么ref实际上比返回一个值类型慢 - 对我来说,在我看来,如果我要在返回它之前编辑很多函数值,那就是它可以更快地引用实际变量来编辑它,而不是在从内存中清除之前不久该变量的实例。

6 个答案:

答案 0 :(得分:11)

“ref”在与性能相同的声望中使用的主要时间是在讨论一些非常典型的情况时,例如在XNA场景中,游戏“对象”通常由结构而不是类来表示,以避免出现问题GC(对XNA产生不成比例的影响)。这对以下内容很有用:

  • 防止在堆栈上多次复制超大结构
  • 防止因修改结构副本而导致数据丢失(XNA结构通常是可变的,与正常做法相反)
  • 允许直接在数组中传递结构,而不是将其复制出来然后重新载入

在所有其他情况下,“ref”通常与附加的副作用相关联,不易在返回值中表示(例如,请参阅Monitor.TryEnter)。

如果你没有像XNA / struct那样的场景,并且没有尴尬的副作用,那么只需使用返回值。除了更典型(其本身具有价值)之外,它可能涉及传递更少的数据(例如,int小于x64上的ref),并且可能需要更少的解除引用。

最后,返回方法更通用;你不想要更新源代码。对比:

// 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)。

答案 1 :(得分:5)

使用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#中使用时会更快一些,但还不足以将其用作性能的goto理由。

这是我在7岁机器上使用下面的代码传递并通过ref和value更新100k字符串的内容。

输出:

迭代:10000000 byref:165ms byval:417ms

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;
}

答案 2 :(得分:3)

我必须同意Ondrej的观点。从风格的角度来看,如果你开始用ref传递所有东西,你最终会与那些想要扼杀你的设计像这样的API的开发人员一起工作!

只需返回方法中的内容,不要让100%的方法返回void。你正在做的将导致非常不干净的代码,并可能混淆最终处理你的代码的其他开发人员。在这里支持清晰度而不是性能,因为无论如何你都不会获得太多的优化。

查看此SO帖子:C# 'ref' keyword, performance

和Jon Skeet的这篇文章:http://www.yoda.arachsys.com/csharp/parameters.html

答案 3 :(得分:1)

首先,不要打扰使用ref是慢还是快。这是不成熟的优化。在99.9999%的情况下,您不会遇到会导致性能瓶颈的情况。

其次,将计算结果作为返回值返回而不是使用ref,因为C语言通常具有“功能”特性。它可以更好地链接语句/调用。

答案 4 :(得分:0)

在您的情况下,使用ref是个坏主意。
使用ref时,程序将从堆栈中读取指针,而不是读取指针所指向的值。
但是如果要按值传递,则只需要从堆栈中读取值,基本上可以将读取量减少一半。

按引用传递仅应用于中型到大型数据结构,例如3D模型或数组。

答案 5 :(得分:0)

对基本数据类型使用ref不是一个好主意。 特别是对于几行代码的简单方法。 首先,C#编译器将进行很多优化,以使代码更快。 根据我的基准https://rextester.com/CQJR12339,通过ref传递会降低性能。 传递引用时,您要复制8bytes作为指针变量(假设使用64位处理器),为什么不直接传递8Bytes double?

通过ref传递对于较大的对象很有用,例如,包含很多字符的字符串。