在不使结构只读的情况下避免使用带有结构的“in”对性能造成的影响?
Posted
技术标签:
【中文标题】在不使结构只读的情况下避免使用带有结构的“in”对性能造成的影响?【英文标题】:Dodging the performance hit from using `in` with a struct without making the struct readonly? 【发布时间】:2018-11-05 11:26:44 【问题描述】:C# 7.2 增加了两个新特性:
在参数中
使用in
作为参数让我们通过引用传递,但随后阻止我们为其分配值。但是性能实际上会变得更糟,因为它会创建结构的“防御性副本”,复制整个内容
只读结构
解决此问题的方法是将readonly
用作struct
。当您将它传递给 in
参数时,编译器会看到它是 readonly
并且不会创建防御性副本,从而使其成为更好的性能替代方案。
这很好,但是struct
中的每个字段都必须是readonly
。这不起作用:
public readonly struct Coord
public int X, Y; // Error: instance fields of readonly structs must be read only
自动属性也必须是readonly
。
有没有办法获得in
参数的好处(编译时检查以强制参数不被更改,通过引用传递)同时仍然能够修改struct
的字段,无需in
的性能是否因创建防御性副本而受到显着影响?
【问题讨论】:
我想你已经回答了你自己的问题,它要么是参考,要么不是,你可以修改它或者你不能,我不认为有适合你所有需求的诚实 感觉就是这样。我只是好奇是否有一些巧妙的方法可以在将防御性副本用作in
参数时延迟加载它。
您的意思是您希望能够修改方法中的字段,但编译器会在此时创建一个副本?感觉会很混乱。 (您希望如何使用它的完整示例会很有帮助,因为我们可以建议替代方案。)
【参考方案1】:
当您将 [a
readonly struct
] 传递给 in 参数时,编译器会看到它是只读的,因此不会创建防御性副本。
我想你误会了。编译器会创建一个包含struct
的只读变量的防御性副本(可以是in
参数,也可以是readonly
字段)当您在该struct
上调用方法时 >.
考虑the following code:
struct S
int x, y;
public void M()
class C
static void Foo()
S s = new S();
Bar(s);
static void Bar(in S s)
s.M();
您可以检查为上述代码生成的 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
。
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
不会被改变(因为它是只读的)。
如果您是change the code to make S
a readonly struct
,则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
注意这里不再复制了。
这是将struct
标记为readonly
避免的防御性副本。但是如果你不在结构上调用任何实例方法,就不会有任何防御性副本。
还要注意,语言规定,当代码执行时,它必须表现得就好像有防御性副本。如果 JIT 可以确定副本实际上不是必需的,则允许避免它。
【讨论】:
为了完整起见:从 c# 8 开始,我们可以将 struct 中的各个方法标记为只读。因此,我们现在可以选择通过仅将 M() 方法标记为只读方式来避免 Bar() 方法中的防御性复制,而无需将整个结构设为只读【参考方案2】:首先,我会不惜一切代价避免使用非只读结构。只有非常强大的性能要求才能证明在无堆分配的热执行路径中使用可变结构是合理的。如果你的结构在其核心是可变的,你为什么要让它只读一种方法呢?这是一条危险且容易出错的道路。
事实:将 in
参数传递与非只读结构相结合将导致在对该副本的引用传递到方法之前产生防御性副本。
因此,任何可变性都将作用于编译器的副本,该副本在方法上下文中可见,而对调用者不可见。令人困惑且不可维护。
我认为in
参数有助于帮助编译器做出明智的决策以获得更好的性能。这绝对不是真的!出于性能原因,我已经尝试过 in
和 readonly
结构,我的结论是:有太多的陷阱实际上会让你的代码变慢。如果您是项目中唯一的开发人员,如果您的结构足够大,您对所有编译器技巧了如指掌,并且经常运行微基准测试......那么您可能会在性能方面受益。
【讨论】:
以上是关于在不使结构只读的情况下避免使用带有结构的“in”对性能造成的影响?的主要内容,如果未能解决你的问题,请参考以下文章
如何在不使 main() 异步的情况下将异步对象注册到 get_it 包?
您可以在不使其无状态的情况下将 Spring Security 与 REST 服务一起使用吗?