【问题标题】:Is there a best practice when a type should be boxed?当一个类型应该被装箱时,是否有最佳实践?
【发布时间】:2018-01-19 22:59:25
【问题描述】:
在 C# 中,有结构和类。结构通常(即存在异常)堆栈分配,类总是堆分配。因此,类实例对 GC 施加压力并且被认为比结构“慢”。 Microsoft 有a best practice guide 何时在类上使用结构。这表示在以下情况下考虑结构:
- 它在逻辑上表示单个值,类似于原始类型(int、double 等)。
- 实例大小小于 16 字节。
- 它是不可变的。
- 不必经常装箱。
在 C# 中,使用大于 16 字节的结构实例通常被认为比垃圾收集的类实例(动态分配)的性能更差。
装箱实例(堆分配的)在速度方面何时比非装箱的等效实例(堆栈分配)表现更好?关于我们何时应该动态分配(在堆上)而不是坚持默认的堆栈分配,是否有任何最佳实践?
【问题讨论】:
标签:
rust
heap-memory
memcpy
boxing
stack-memory
【解决方案1】:
TL;DR:从不拳击开始,然后是个人资料。
堆栈分配与盒装分配
这可能更明确:
虽然语义写作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,就没有问题。
尽管有这些奇怪的情况,但我建议尽可能坚持使用堆栈,除非分析显示有利于装箱的地方。
内联会员或盒装会员
这个案例要复杂得多:
因此,这是一个非常好的平衡行为。对成员进行装箱或拆箱可能会提高代码库某些部分的性能,同时降低其他部分的性能。
绝对没有一刀切的。
因此,我再次建议避免装箱,直到剖析显示有利于装箱的地方。
1考虑到在 Linux 上,任何在进程中没有空闲内存的内存分配都可能需要系统调用,如果操作系统中没有空闲内存可能会触发OOM 杀手杀死一个进程,此时它的内存被抢救并可用。一个简单的malloc(1) 可能很容易需要毫秒。