2017-08-11 76 views
6

在C#中,有结构和类。结构通常(即有例外)分配堆栈并且类总是分配堆。因此,类实例向GC施加压力,被认为比结构“慢”。微软有a best practice guide何时使用类结构。这是说要考虑,如果一个结构:类型应该是盒装的最佳做法吗?

  • 它在逻辑上表示单个值,类似于原始类型(int,double等)。
  • 它有一个16字节以下的实例大小。
  • 它是不可变的。
  • 它不会经常被装箱。

在C#,使用大于16个字节,一般所述结构实例比垃圾收集类实例来执行更差(动态分配)。

何时装箱实例(堆分配)在速度方面比非盒装等效实例(堆栈分配)更好?关于何时应该动态分配(在堆上)而不是坚持默认的堆栈分配有什么最佳做法吗?

回答

6

TL; DR:从无拳击开始,然后轮廓。


堆栈分配VS盒装分配

这也许是更明确的:

  • 坚持到堆栈,
  • 除非值足够大,它会吹它起来。

虽然语义写作fn foo() -> Bar意味着从被调用框架调用者帧移动Bar,实际上,你更有可能以fn foo(__result: mut * Bar)签名相当于是主叫其堆栈分配空间,并传递到结束一个指向被调用者的指针。

这可能并不总是足以避免复制,因为一些模式可能会阻止直接返还口写着:

fn defeat_copy_elision() -> WithDrop { 
    let one = side_effectful(); 
    if side_effectful_too() { 
     one 
    } else { 
     side_effects_hurt() 
    } 
} 

这里,也没有神奇:

  • 如果编译器使用one的返回槽,那么在分支评估为false的情况下,它必须将one移出然后将新的WithDrop实例化到其中,最后销毁one,
  • 如果编译器在当前栈上实例化one,并且必须返回它,那么它必须执行一个副本。

如果类型不需要Drop,那就没有问题了。

尽管有这些古怪的情况下,我建议坚持堆栈如果可能的话,除非分析显示它会是有益的箱子的地方。


联成员或盒装会员

这种情况要复杂得多:

  • struct/enum的大小会受到影响,因此CPU的缓存行为的影响:

    • 少FR equently使用大变形是拳击(或他们的拳击部分)的候选资格,
    • 不经常访问的大成员拳击一个很好的候选人。
  • 的同时,也有拳击费用:

    • 它与Copy类型不兼容,并含蓄地实现Drop(正如上面看到的,禁用某些优化),
    • 分配/释放存储器具有极大等待时间,
    • 访问盒装存储器介绍了数据依赖性:可以不知道哪个高速缓存线知道地址之前请求。

结果,这是一个非常精细的平衡。对成员进行装箱或拆箱可能会提高代码库某些部分的性能,同时降低其他代码的性能。

肯定是有没有一个放之四海而皆准。

因此,再一次,我建议避免拳击,直到分析揭示了它会是有益的箱子的地方。

考虑在Linux上,任何内存分配针对其存在的过程中没有多余的内存可能需要一个系统调用,如果在操作系统没有备用内存可能触发OOM杀手杀死进程,在此时它的内存被抢救并可用。简单的malloc(1)可能很容易要求毫秒