【问题标题】:The performance cost to using ref instead of returning same types?使用 ref 而不是返回相同类型的性能成本?
【发布时间】:2011-11-13 10:37:38
【问题描述】:

您好,这确实让我很困扰,我希望有人能回答我。我一直在阅读有关ref(和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 确实比返回值类型慢 - 在我看来,如果我要在返回函数值之前对其进行大量编辑,那将是更快地引用实际变量来编辑它,而不是在它从内存中清除之前不久的那个变量的实例。

【问题讨论】:

  • 出于文体原因,我通常更喜欢返回一个值,因为我喜欢无副作用的函数。
  • 您想计算性能成本吗?询问分析员,不要问我们!
  • 第一种情况更容易阅读,因为您已经相应地命名了您的方法。像_value = Add(_value, _add);_value = SumOf(_value, _add); 这样的东西对我来说更具可读性。
  • C# 'ref' keyword, performance 的可能重复项

标签: c# performance ref


【解决方案1】:

“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 的使用甚至不太清楚

【讨论】:

  • 谢谢,这准确地解释了我在哪里使用和不想使用 ref 以及为什么!最棒的是,你给了我很多东西让我看看,否则我不会有。
  • ref 的一个重要优点,但是,引用您的示例:如果某些内容通过引用传递给像 Add 之类的方法,则该方法可能会以无锁、非阻塞,原子,线程安全的方式。因此,如果两个线程同时调用第二个示例中所示的“添加”方法,x 将添加十个。相比之下,如果一个或两个线程如第一个示例所示进行更新,则一个线程可能会撤消另一个线程执行的更新。
  • @Marc Gravell:“添加”方法可以在内部执行此操作。当然,只有在操作需要如此小心的事物的所有代码都使用 Interlocked 方法时,这样做才有价值,但如果所讨论的事物是自定义数据类型,这是一种现实的可能性。
【解决方案2】:

使用 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 岁的机器上使用下面的代码通过引用和值传递和更新 100k 字符串的结果。

输出:

迭代次数:10000000 参考:165ms 延时: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;
}

【讨论】:

  • 在对象的那个大小下,创建一个新副本当然会更昂贵。对于大多数正常情况,我认为不会有那么大的差异。
  • 所有抛出结果都是正确的,两个函数的动作不一样。实际上,一个功能是返回一些东西,另一个不是。单独的 ref 关键字与非 ref 关键字相比并不快,但几乎相同,如果不是更慢的话。要做出正确的测量,使两个函数具有相同的操作,并且只有 ref 必须更改...
【解决方案3】:

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

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

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

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • 我总是将这些“这是过早优化”的答案解释为“我不知道”。
    • 其实我知道并且知道如何衡量它。但是,浪费时间完全没有意义,因为我们谈论的是每次调用的几个 CPU 周期差异。
    • 我不喜欢(与)开发人员在非性能关键领域的一些编码风格/设计/架构之间进行选择时总是将性能作为主要案例。经常发生。
    • 是的。伟大的。我来这里是为了寻找这些信息,因为我处理的是高性能交易应用程序。在某些情况下,您希望转移到结构以减轻 gc 的负载...
    • 我认为了解性能影响是件好事。例如,装箱/拆箱会影响性能,应该避免。没有分析器会将装箱显示为性能问题,但如果过度使用仍会降低性能。编写代码时很高兴知道。
    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

        猜你喜欢
        • 2016-04-05
        • 2022-06-12
        • 2015-11-17
        • 2018-06-26
        • 1970-01-01
        • 2021-05-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多