函数参数传递中C#7.2常量与引用(in)只读字段的比较

函数参数传递中C#7.2常量与引用(in)只读字段的比较,c#,.net,.net-core,c#-7.2,C#,.net,.net Core,C# 7.2,比如说,我有一门课: 常数 只读字段 private readonly decimal y = 2.0m; 具有此签名的方法 void Method1(in decimal x) 如果我用const x Method1(x)调用Method1,我假设x的值将通过value传递,而当我用readonly y Method1(y)调用Method1时,值将通过readonly引用传递。因此,我的期望是,用作“伪”常量的只读字段的性能越高。我错了?我有一些疑问,在内部,编译器或clr将使用cons

比如说,我有一门课: 常数

只读字段

private readonly decimal y = 2.0m;
具有此签名的方法

void Method1(in decimal x)

如果我用const x Method1(x)调用Method1,我假设x的值将通过value传递,而当我用readonly y Method1(y)调用Method1时,值将通过readonly引用传递。因此,我的期望是,用作“伪”常量的只读字段的性能越高。我错了?我有一些疑问,在内部,编译器或clr将使用const作为通过引用传递的只读字段

我做了一个测试,获胜者是传入的只读字段,这是使用i7 8700k处理器执行的代码

class Test
{
    public const decimal x = 0.2m;
    public readonly decimal y = 0.2m;

    public void Method1(in decimal x)
    {
        Thread.MemoryBarrier();
    }
}

class Program
{
    static void Main(string[] args)
    {
        var t = new Test();

        var sw = new Stopwatch();
        sw.Start();
        for (var i = 0; i < 100000000; i++)
        {
            t.Method1(in t.y);
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedTicks);
        sw.Restart();
        for (var i = 0; i < 100000000; i++)
        {
            t.Method1(Test.x);
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedTicks);
        Console.ReadLine();
     } 
}
类测试
{
公共常数十进制x=0.2m;
公共只读十进制y=0.2m;
公共作废方法1(十进制x)
{
Thread.MemoryBarrier();
}
}
班级计划
{
静态void Main(字符串[]参数)
{
var t=新测试();
var sw=新秒表();
sw.Start();
对于(变量i=0;i<100000000;i++)
{
t、 方法1(t.y);
}
sw.Stop();
控制台写入线(软件ElapsedTicks);
sw.Restart();
对于(变量i=0;i<100000000;i++)
{
t、 方法1(试验x);
}
sw.Stop();
控制台写入线(软件ElapsedTicks);
Console.ReadLine();
} 
}
作为t.Method1调用的方法(在t.y中);消耗了11606428滴答声, 作为t.Method1(Test.x)调用的方法;已消费16963941只蜱

因此,在这种情况下,没有对const进行优化。是的,只读选项的性能可能更高,但其性能增益是否相关仍有争议

为什么?

const
版本实际上会在每次调用该方法时运行decimal构造函数,而readonly/in版本只会将引用复制到先前创建的decimal实例。后者显然更快

您可以通过检查IL来验证这一点:

  • 常量版本:

    IL_0001: ldc.i4.s 20
    IL_0003: ldc.i4.0
    IL_0004: ldc.i4.0
    IL_0005: ldc.i4.0
    IL_0006: ldc.i4.1
    IL_0007: newobj instance void [mscorlib]System.Decimal::.ctor(int32, int32, int32, bool, uint8)
    IL_000c: call instance bool C::Method2(valuetype [mscorlib]System.Decimal)
    
  • 只读/输入版本:

    IL_0002: ldflda valuetype [mscorlib]System.Decimal C::dd
    IL_0007: call instance bool C::Method1(valuetype [mscorlib]System.Decimal&)
    IL_000c: pop
    
也就是说,我不完全清楚为什么要将
const
与性能联系起来。
const
是一种在代码中传达给定值永远不会改变的方式
const
并不意味着存储为常量的该值在使用时性能会更好


除非您真的有一个通过这种边际(最多)优化解决的经验证明的性能问题,否则逻辑上为常量的值应为
常量
,逻辑上为只读值的变量应为
只读
,并且应忽略所有其他注意事项。如果您的情况是前者,那么请在代码中清楚地记录为什么逻辑常量值被实现为只读字段。

“性能越高”为什么不简单地测量它?但是做一个基准测试,这是在一个循环中执行它数百万次。只做一次不会给你带来实际的结果。无论如何,我认为两者之间的区别——如果有的话——是可以忽略的。这些说法并不等同。常量将在编译期间消失。编译器可以并且将用它的实际值替换对它的任何引用。事实上,这是一个陷阱——如果任何程序集依赖于该常量,那么所有程序集都必须重新编译,或者最终使用不同的值<代码>常量应为good@PanagiotisKanavos事实上,如果您实际反编译上述情况,您将看到使用in关键字将产生不同。因此,它只是有点消失了,但看一看:@RobinB该副本是在和
ref
中使用
的众所周知的陷阱之一。在这里创建副本的不是
中的
。编译器不确定该值不会更改,因此会复制该值以确保不会更改。至少有一个Roslyn分析仪可以找到这样的结果problems@RobinB发现它:在某些上下文中,一切都是相关的,比如我的。我在写HFTbot@MarcoLevarato那你找错东西了。十进制的
已为只读。正如这里的答案所示,像这样的过早优化会导致性能下降。更好的排序、分组算法将为HFT带来更好的性能。更好的容器、避免浪费的操作、更好的算法etc@MarcoLevarato当然,所有这些在某种程度上都是相关的。但是如果这个问题是一个真实的、可测量的性能问题,那么这里的问题可能是您没有使用最合适的框架。。。也许非托管环境是更好的选择?@Marcelavarat记得有人试图在HFT应用程序中找到threashold以上的最后一个值。在C++中为PrF编写的。然后从开始而不是结束开始迭代以查找最后一个值。用反向迭代器替换这些,添加映射(字典),使用适当的容器和算法,至少可以提高10倍。没有任何指针算法可以做到这一点——没有什么需要优化的——在这两种情况下,您都要求引用某个内容。必须存在某种东西,无论是字段还是本地副本。如果参数d不是a
ref
,您会看到常量值内联到`f(2m)`
IL_0002: ldflda valuetype [mscorlib]System.Decimal C::dd
IL_0007: call instance bool C::Method1(valuetype [mscorlib]System.Decimal&)
IL_000c: pop