14

嗨,这真的很困扰我,我希望有人能回答我。我一直在阅读有关ref(and out) 的内容,并且试图弄清楚我是否正在使用refs 减慢我的代码速度。通常我会替换类似的东西:

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真的比返回值类型慢 - 在我看来,如果我要在返回之前大量编辑函数值,那么引用实际变量会更快在从内存中清除之前不久编辑它而不是该变量的实例。

4

6 回答 6

13

在同一个句子中使用“ref”作为 performance 的主要时间是在讨论一些非常非典型的情况时,例如在 XNA 场景中,游戏“对象”通常由结构而不是类表示以避免 GC 问题(对 XNA 有不成比例的影响)。这对以下内容很有用:

  • 防止在堆栈上多次复制过大的结构
  • 防止由于更改结构副本而导致数据丢失(XNA 结构通常是可变的,违反正常做法)
  • 允许直接在数组中传递一个结构,而不是把它复制出来再复制回来

在所有其他情况下,“ref”通常与额外的副作用相关联,不容易在返回值中表达(例如,参见 参考资料Monitor.TryEnter)。

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

最后,return 方式更加通用;您并不总是想更新源。对比:

// 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”紧随其后),并且在不明确的语言(例如 VB )中,ref 的使用甚至更不清晰。

于 2011-11-13T11:00:12.263 回答
6

使用 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 和值传递和更新 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;
}
于 2013-11-26T05:20:01.753 回答
4

我必须同意 Ondrej 的观点。从风格上看,如果你开始传递所有东西,ref你最终会与想要扼杀你设计这样的 API 的开发人员合作!

只需从方法中返回内容,不要让 100% 的方法返回void。你正在做的事情会导致代码非常不干净,并且可能会让其他最终使用你的代码的开发人员感到困惑。在这里优先考虑性能的清晰度,因为无论如何您都不会在优化方面获得太多收益。

检查这个 SO 帖子:C# 'ref' 关键字,性能

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

于 2011-11-13T10:51:54.693 回答
1

首先,不要在意使用ref速度是慢还是快。这是过早的优化。在 99.9999% 的情况下,您不会遇到会导致性能瓶颈的情况。

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

于 2011-11-13T10:46:13.010 回答
0

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

通过引用传递只能用于大中型数据结构,例如 3D 模型或数组。

于 2020-03-11T13:53:44.457 回答
0

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

通过 ref 在较大对象的情况下很有用,例如具有大量字符的字符串。

于 2020-05-07T10:06:05.783 回答