C# 7.2 const vs referenced(in) 函数参数传递中的只读字段

Posted

技术标签:

【中文标题】C# 7.2 const vs referenced(in) 函数参数传递中的只读字段【英文标题】:C# 7.2 const vs referenced(in) readonly field in function parameters passing 【发布时间】:2019-01-09 00:21:29 【问题描述】:

假设我有一堂课: 一个常量

private const decimal x = 2.0m;

只读字段

private readonly decimal y = 2.0m;

具有此签名的方法

void Method1(in decimal x)

如果我使用 const x Method1(x) 调用 Method1,我假设 x 的值将按值传递,而不是当我将 Method1 与只读 y Method1(in y) 一起使用时,值将通过只读引用传递.所以我的期望是性能更高的是用作“假”常量的只读字段。我错了?我有一些疑问,编译器将在内部使用 const 或由 clr 优化,以作为通过引用传递的只读字段的性能。

【问题讨论】:

“性能更高” 为什么不简单地测量一下呢?但是做一个基准测试,这是在循环中执行数百万次。只做一次不会显示出现实的结果。无论如何,我认为差异 - 如果有的话 - 可以忽略不计。 这些语句等效。一个 const 将在编译期间消失。编译器可以并且用它的实际值替换对它的任何引用。事实上,这是其中一个问题 - 如果 any 程序集依赖于该常量,则 all 将不得不重新编译它们或最终使用不同的值。 Constant 应该永远保持不变 @PanagiotisKanavos 实际上,如果您实际反编译上述情况,您会发现使用 in 关键字会有所作为。所以它只是消失了,但看看:sharplab @RobinB 该副本是使用inref 的著名陷阱之一。不是in 在此处创建副本。 编译器 不确定该值不会改变,因此它复制 以确保它不会改变。至少有一台 Roslyn 分析仪可以查找此类问题 @RobinB 找到它:ErrorProne.NET 【参考方案1】:

我做了一个测试,获胜者是传入的只读字段,这是使用 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();
      

调用的方法为 t.Method1(in t.y);消耗了 11606428 个刻度, 调用的方法为 t.Method1(Test.x);已消耗 16963941 个滴答声

因此,在这种情况下,不会对 const 进行优化

【讨论】:

没有什么可优化的——在这两种情况下,您都要求对某些东西进行引用。那么必须存在某些东西,无论是作为字段还是本地副本。如果参数不是ref,你会看到常量值被内联到`f(2m);`【参考方案2】:

是的,可能 readonly 选项的性能更高,但对于这种性能提升是否相关存在争议。

为什么?

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

您可以通过简单地检查 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)

只读/in 版本:

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 并不意味着,这个值存储为常量,使用时性能会更好。

除非您确实有一个通过这种边际(充其量)优化解决的经验证明的性能问题,否则逻辑上为常数的值应该是const,而逻辑上是只读值的变量应该是readonly 和所有其他考虑应被忽略。如果您的情况是前者,那么请在您的代码中清楚地记录为什么将逻辑上的常量值实现为只读字段。

【讨论】:

all 在某些方面与 my.我正在写一个 HFT 机器人 @MarcoLevarato 那么您正在寻找错误的东西。 decimal 已经是只读的。正如这里的答案所示,像这样的超早优化可能会导致性能下降。更好的排序、分组算法将为 HFT 带来 far 更好的性能。更好的容器,避免浪费的操作,更好的算法等 @MarcoLevarato 当然在某种程度上一切都是相关的。但是,如果这个问题是一个真实且可衡量的性能问题,那么这里的问题可能是您没有使用最合适的框架......也许非托管环境是更好的选择? @MarcoLevarato 记得有人试图在 HFT 应用程序中找到高于阈值的最后一个值。用 C++ 编写它以获得性能。然后从 start 而不是 end 开始迭代以找到 last 值。用反向迭代器替换这些,添加映射(字典),使用适当的容器和算法,至少可以提高 10 倍。没有指针算法可以做到这一点

以上是关于C# 7.2 const vs referenced(in) 函数参数传递中的只读字段的主要内容,如果未能解决你的问题,请参考以下文章

关于Unity C# 的Value Type (值类型) vs. Reference Type (引用类型),优缺点?GC ? ECS?

Visual Studio References 显示未引用的 NuGet 包

C++ pass-by-non-const-reference 方法在 pass-by-const-reference 方法中

C++ 之 const references

为啥我们使用 const 和 reference 作为参数来复制构造函数?

无法在 Clang 上将 std::reference<T> 转换为 std::reference<const T>