C# 避免在结构中使用'in',而不使结构为只读,从而影响性能?

C# 避免在结构中使用'in',而不使结构为只读,从而影响性能?,c#,optimization,struct,c#-7.2,in-parameters,C#,Optimization,Struct,C# 7.2,In Parameters,C#7.2增加了两个新功能: 在参数中 在中为参数使用,让我们通过引用传递,但这样会阻止我们为其赋值。但是,性能实际上可能会变得更差,因为它会创建结构的“防御副本”,复制整个结构 只读结构 解决此问题的一种方法是对结构使用readonly。当您将它传递到in参数中时,编译器会看到它是只读的,并且不会创建防御副本,从而使它成为执行性能的更好选择 这很好,但是struct中的每个字段都必须是readonly。这不起作用: public readonly struct Coord { pu

C#7.2增加了两个新功能:

  • 在参数中

    在中为参数使用
    ,让我们通过引用传递,但这样会阻止我们为其赋值。但是,性能实际上可能会变得更差,因为它会创建结构的“防御副本”,复制整个结构

  • 只读结构

    解决此问题的一种方法是对
    结构使用
    readonly
    。当您将它传递到
    in
    参数中时,编译器会看到它是
    只读的
    ,并且不会创建防御副本,从而使它成为执行性能的更好选择

  • 这很好,但是
    struct
    中的每个字段都必须是
    readonly
    。这不起作用:

    public readonly struct Coord
    {
        public int X, Y;    // Error: instance fields of readonly structs must be read only
    }
    
    自动属性也必须是只读的

    是否有一种方法可以获得
    in
    参数(编译时检查以强制参数未更改,通过引用传递)的好处,同时仍然能够修改
    结构的字段,而不会因为创建防御副本而导致
    in
    的性能显著下降

    当您将[a
    readonly struct
    ]传递给in参数时,编译器会看到它是只读的,不会创建防御副本

    我想你误解了。当您对该
    结构
    调用方法时,编译器将创建一个只读变量的防御副本,该变量包含一个
    结构
    (可以是
    中的
    ,也可以是
    只读
    字段)

    考虑:

    您可以检查为上述代码生成的IL,以查看实际发生的情况

    对于
    Foo
    ,IL为:

    ldloca.s 0 // load address of the local s to the stack
    initobj S  // initialize struct S at the address on the stack
    ldloca.s 0 // load address of the local s to the stack again
    call void C::Bar(valuetype S&) // call Bar
    ret        // return
    
    请注意,没有复制:本地
    s
    被初始化,然后该本地的地址直接传递到
    Bar

    条的IL为:

    ldarg.0     // load argument s (which is an address) to the stack
    ldobj S     // copy the value from the address on the stack to the stack
    stloc.0     // store the value from the stack to an unnamed local variable
    ldloca.s 0  // load the address of the unnamed local variable to the stack
    call instance void S::M() // call M
    ret         // return
    
    在这里,
    ldobj
    stloc
    指令创建防御副本,以确保如果
    M
    突变了
    struct
    s
    不会突变(因为它是只读的)

    如果选择,则
    Foo
    的IL保持不变,但
    Bar
    的IL更改为:

    ldarg.0 // load argument s (which is an address) to the stack
    call instance void S::M() // call M
    ret     // return
    
    请注意,这里不再复制

    这是将结构标记为只读的防御性副本。但是如果不在结构上调用任何实例方法,就不会有任何防御副本


    还要注意的是,该语言规定,当代码执行时,它的行为必须像防御性副本一样。如果JIT能够发现副本实际上不是必需的,那么就允许避免它。

    首先,我会不惜一切代价避免非只读结构。只有非常强大的性能需求才能证明在堆自由分配、热执行路径中使用可变结构是合理的。如果您的结构在其核心是可变的,那么为什么要使它对于一个方法是只读的呢?这是一条危险的、容易出错的道路

    事实:将
    中的
    参数传递与非只读结构相结合将导致在将对该副本的引用传递到方法之前生成防御副本

    因此,任何可变性都将在方法上下文中可见的编译器副本上工作,而对调用方不可见。混乱且不可维护


    我认为
    中的
    参数有助于编译器做出明智的决策以获得更好的性能。那绝对不是真的!出于性能原因,我在
    只读结构中尝试了
    ,我的结论是:有太多的陷阱实际上会使代码变慢。如果您是项目中唯一的开发人员,如果您的结构足够大,那么您对编译器的所有技巧都了如指掌,并且您经常运行微基准测试。。。然后你可能会在性能方面受益。

    我认为你已经回答了自己的问题,它要么是参考,要么不是,你可以修改它,要么不能,我不认为有一个适合你所有需求的诚实的感觉就是这样。我只是好奇,当防御性副本用作
    中的参数时,是否有聪明的方法延迟加载该副本。您的意思是您希望能够修改方法中的字段,但编译器会在此时创建副本?那感觉会很混乱。(一个完整的示例,说明您希望如何使用它会很有帮助,因为我们可以建议其他方法。)为了完整性:由于c#8,我们可以将struct中的各个方法标记为只读。因此,我们现在可以选择在Bar()方法中避免防御性复制,只将M()方法标记为readonly,而不使整个结构为readonly
    ldarg.0 // load argument s (which is an address) to the stack
    call instance void S::M() // call M
    ret     // return