C#编译器可以折叠一个返回常量的函数吗?

时间:2018-04-18 10:21:44

标签: c# compilation

这是我过去用BenchmarkDotNet测试的程序。我正在比较out与元组的表现。

public class OutVsTuple
{
    [Benchmark]
    public void Out()
    {
        void OutLocal(out int a, out int b)
        {
            a = 1;
            b = 2;
        }

        OutLocal(out int x, out int y);
        int z = x + y;
    } 

    [Benchmark]
    public void Tuple()
    {
        (int a, int b) TupleLocal() => (1,2);

        (int x, int y) = TupleLocal();
        int z = x + y;
    } 
}

class Program
{
    static void Main(string[] args)
    {
        BenchmarkRunner.Run<OutVsTuple>();
    }
}

结果:

 Method |      Mean |     Error |    StdDev |    Median |
------- |----------:|----------:|----------:|----------:|
    Out | 0.0000 ns | 0.0000 ns | 0.0000 ns | 0.0000 ns |
  Tuple | 5.3041 ns | 0.1422 ns | 0.3209 ns | 5.1754 ns |

我认为Out的时间是0.0000,因为整个函数的结果可以知道,这使得该方法毫无意义。我改变它就像这样使用Random

public class OutVsTuple
{
    System.Random r = new System.Random();

    [Benchmark]
    public void Out()
    {
        void OutLocal(out int a, out int b)
        {
            a = r.Next(0,100);
            b = r.Next(0,100);
        }

        OutLocal(out int x, out int y);
        int z = x + y;
    } 

    [Benchmark]
    public void Tuple()
    {
        (int a, int b) TupleLocal() => (r.Next(0,100),r.Next(0,100));

        (int x, int y) = TupleLocal();
        int z = x + y;
    } 
}

class Program
{
    static void Main(string[] args)
    {
        BenchmarkRunner.Run<OutVsTuple>();
    }
}

结果:

 Method |     Mean |     Error |    StdDev |
------- |---------:|----------:|----------:|
    Out | 27.10 ns | 0.5799 ns | 1.2233 ns |
  Tuple | 28.52 ns | 0.5486 ns | 0.4863 ns |

现在结果看起来更合理。

  1. 编译器是否有任何机制来折叠函数,就像我猜到的那样?
  2. 为什么元组函数需要花费时间才能轻松知道结果,如out情况?
  3. 聚苯乙烯。经过多次运行后,第一个案例最终返回非零,但仍然非常低。

     Method |      Mean |     Error |    StdDev |
    ------- |----------:|----------:|----------:|
        Out | 0.0022 ns | 0.0080 ns | 0.0075 ns |
      Tuple | 5.0376 ns | 0.1484 ns | 0.1823 ns |
    

1 个答案:

答案 0 :(得分:1)

  

为什么元组函数需要时间才能很容易地知道结果?

这就是你的错误:元组版本实际上正在创建ValueTuple结构的新实例。

带有out参数的第一个版本相当于对常量的两个简单赋值。 元组版本实际上在概念上等同于:

var newTuple = new ValueTuple(1, 2);
var a = newTuple.Item1;
var b = newTuple.Item2;

并且需要更多时间,因为新的实例已经在运行时创建了,因此无法进行优化。

如果您尝试在DataRow中使用元组,则可以看到元组不是常量:

[DataTestMethod]
[DataRow( (1, 2) )]  // won't compile
void SomeTest( (double, double) args ) { }

编译器会抱怨你需要一个常量表达式,而元组则不是。