在不使结构只读的情况下避免使用带有结构的“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

这里,ldobjstloc 指令创建防御性副本,以确保如果 M 改变了 structs 不会被改变(因为它是只读的)。

如果您是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 参数有助于帮助编译器做出明智的决策以获得更好的性能。这绝对不是真的!出于性能原因,我已经尝试过 inreadonly 结构,我的结论是:有太多的陷阱实际上会让你的代码变慢。如果您是项目中唯一的开发人员,如果您的结构足够大,您对所有编译器技巧了如指掌,并且经常运行微基准测试......那么您可能会在性能方面受益。

【讨论】:

以上是关于在不使结构只读的情况下避免使用带有结构的“in”对性能造成的影响?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使 main() 异步的情况下将异步对象注册到 get_it 包?

在不使浏览器崩溃的情况下分析 JavaScript

如何在不使项目变灰的情况下禁用 UITabBarItem

您可以在不使其无状态的情况下将 Spring Security 与 REST 服务一起使用吗?

SQLAlchemy:如何在不使其成为主键的情况下使整数列 auto_increment (且唯一)?

是否可以在不使背景变暗的情况下在 iPad 上呈现模态视图控制器?