【问题标题】:Multiple return variables - which has best performance (out, tuple, class)?多个返回变量 - 哪个具有最佳性能(out、tuple、class)?
【发布时间】:2021-10-05 21:08:39
【问题描述】:

返回多个双精度的方法可以通过多种方式实现:

通过out参数:

class MyClass
{
    static double Add3(double x, out double xp1, out double xp2)
    {
        xp1 = x + 1.0;
        xp2 = x + 2.0;
        return x + 3.0;
    }
}

通过元组:

class MyClass
{
    static Tuple<double, double, double> Add3(double x)
    {
        Tuple<double, double, double> ret = new Tuple<double, double, double>();
        ret.Item1 = x + 1.0;
        ret.Item2 = x + 2.0;
        ret.Item3 = x + 3.0;
        return ret;
    }

通过一个类收集结果:

class MyClass
{
    class Result
    { 
        double xp1;
        double xp2;
        double xp3;
    }

    static Result Add3(double x)
    {
        Result ret = new Result
        {
            xp1 = x + 1.0;
            xp2 = x + 2.0;
            xp3 = x + 3.0;
        }
        return ret;
    }
}

我从 cmets 到 this question 的印象是,人们普遍认为额外课程的方法是最佳实践。不过,我想知道这三个变体对运行时性能的影响是否存在经验法则。

out 参数相比,Tuple 或类的构造函数是否需要额外的时间?

特别是,带有out 参数的变体在实际只使用一个结果双精度的情况下是否具有任何性能优势,例如在以下 sn-p 中?

double zPlus3 = MyClass.Add3(z, out _, out _)

【问题讨论】:

  • 如果您有两匹马并且您想知道这两匹马中哪一匹更快,那么与您的马赛跑:ericlippert.com/2012/12/17/performance-rant
  • 该类需要进行垃圾收集。您可以通过使用值元组或使用结构而不是类来避免这种情况。
  • 我投票决定重新提出这个问题,因为我看不出它是如何基于意见的。基于适当基准的结果没有任何意见。另外我想指出,在与性能相关的问题上发布指向the rant 的链接是粗鲁的。这是一种间接的说法,即“不要用你的愚蠢问题来打扰我们,我们有比为你编写基准更好的事情要做”。我的观点是,如果你不喜欢为别人做基准测试,没关系。跳过这个问题。没有必要用咆哮来回答问题。
  • @AmosEgel 非常广泛的主题。这取决于设计、“同时”操作的实体数量、数据成员的数量及其类型、操作的实例数量和 proc 调用的数量、所做的修改以及目标 x32、x64 , arm... 您当然可以在网上或书籍中找到各种文章,或者花费大量时间创建自己的文章(非常有趣的领域)。但总的来说,如果我没记错的话,很少有数据使用原子参数或结构进行优化,否则使用类,因为在调用/rets 期间只有指针被 cpu 堆栈消耗。
  • 取消该问题资格的原因:因为它太宽泛,也容易基于意见的答案。 Stack Overflow 不是为了一般的理论论证,它是为了回答具体的问题。您的问题是一个合法的问题,而不是一个合法的Stack Overflow 问题。如果你想要我的意见,我会说使用一个类(或Tuple&lt;&gt;,它也是一个类)会影响性能,因为它需要垃圾收集,所以我会考虑不要在紧密循环中使用它。但在大多数情况下,它根本不可能产生任何影响。

标签: c# .net performance out


【解决方案1】:

为了给这个问题添加一些确凿的事实,here 是一个比较这些替代方案性能的基准项目。

令人惊讶的是,.NET 对 ValueTuple 和 KeyValuePair 进行了大量优化, 与 Debug 相比,Release 模式下的执行时间缩短了 34 倍!

在发布模式下,所有实现都具有相似的速度, 除了返回 Tuple{int,int} 由于垃圾收集量大,速度慢了 10 倍。

在调试模式下,只有使用 Out-Parameters 的方法速度很快。 从 Debug 到 Release Build 的相对速度因子在最后一列“*”中给出

Method Release Debug *
Return Tuple 510.84 ms 1,515.2 ms 3
Return KeyValuePair 44.56 ms 1,527.1 ms 34
Return ValueTuple 51.28 ms 1,418.6 ms 28
Return NullableValue 48.41 ms 1,527.0 ms 29
2 out Parameters 43.83 ms 560.4 ms 14
1 out Parameter 48.41 ms 586.5 ms 13
Return Single Value 49.72 ms 523.8 ms 11

【讨论】:

  • 我认为调试测量是多余的。任何意识到其应用性能的人都不会发布它的 Debug 版本!
【解决方案2】:

通过 Tuple&lt;T1,T2,T3&gt; 或具有 3 个属性的自定义类返回多个值在性能方面是等效的。元组更容易获得(您不必对它们进行编码),而自定义类更方便使用,但这两种方法都涉及引用类型的实例化,它必须是堆分配的,然后是垃圾收集。如果您只使用这些类型来访问它们的属性一次,那么您将没有任何附加值来补偿堆分配/垃圾收集开销。在这种情况下,使用out 参数在性能方面具有优势。不过,还有第四个解决方案,它结合了所有这些方法的优点:value tuples(在 C# 7.0 及更高版本中可用)。

static (double, double, double) Add3(double x)
{
    return (x + 1.0, x + 2.0, x + 3.0);
}

使用示例,演示tuple deconstruction

(double xp1, double xp2, double xp3) = Add3(13);

...或等效地使用类型推断:

var (xp1, xp2, xp3) = Add3(13);

优点:

  1. ValueTuple&lt;T1,T2,T3&gt;Tuple&lt;T1,T2,T3&gt; 一样容易获得。
  2. changing the field namesValueTuple&lt;T1,T2,T3&gt; 的语言支持比 Item1Item2Item3 更有意义,使它们(几乎)对自定义类同样方便。
  3. ValueTuple&lt;T1,T2,T3&gt; 存储在堆栈中,就像out 参数一样。不涉及堆分配和垃圾回收。

【讨论】:

    猜你喜欢
    • 2013-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-01
    • 2019-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多