C# 避免在结构中使用'in',而不使结构为只读,从而影响性能?
C#7.2增加了两个新功能: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
,让我们通过引用传递,但这样会阻止我们为其赋值。但是,性能实际上可能会变得更差,因为它会创建结构的“防御副本”,复制整个结构
结构使用readonly
。当您将它传递到in
参数中时,编译器会看到它是只读的
,并且不会创建防御副本,从而使它成为执行性能的更好选择
struct
中的每个字段都必须是readonly
。这不起作用:
public readonly struct Coord
{
public int X, Y; // Error: instance fields of readonly structs must be read only
}
自动属性也必须是只读的
是否有一种方法可以获得in
参数(编译时检查以强制参数未更改,通过引用传递)的好处,同时仍然能够修改结构的字段,而不会因为创建防御副本而导致in
的性能显著下降
当您将[areadonly 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