.NET 8中值类型转接口无额外内存分配的原因探究
结构体转接口的装箱操作为何无额外内存分配?
我在.NET 8环境下执行如下基准测试,结果显示没有额外内存分配,但结构体转换为接口应该会涉及装箱操作。用Rider分析IL代码后,确实看到了box和unbox.any指令,想了解背后的原因。
基准测试代码
[MemoryDiagnoser] public class Test { [Benchmark] public Color Test123() { Color color = new Color(); IFormattable formattable = color; return (Color)formattable; } [Benchmark] public int Test234() { int number = 114514; IConvertible convertible = number; return (int)convertible; } } [StructLayout(LayoutKind.Sequential)] public struct Color : IFormattable { public double R { get; set; } public double G { get; set; } public double B { get; set; } public double A { get; set; } public static Color GetRandomColor() { Random rand = new Random(); double r = rand.Next(0, 256) * rand.NextDouble(); double g = rand.Next(0, 256) * rand.NextDouble(); double b = rand.Next(0, 256) * rand.NextDouble(); double a = rand.Next(0, 256) * rand.NextDouble(); return new Color(r, g, b, a); } public string ToString(string? format, IFormatProvider? formatProvider) { return $"{R} {G} {B} {A}"; } public Color(double r, double g, double b, double a) { R = r; G = g; B = b; A = a; } public static bool operator ==(Color left, Color right) { return left.R == right.R && left.G == right.G && left.B == right.B && left.A == right.A; } public static bool operator !=(Color left, Color right) { return !(left == right); } }
分析得到的IL代码(Test123方法)
.method public hidebysig static valuetype Program.Color Test123() cil managed { .maxstack 1 .locals init ( [0] valuetype Program.Color color, [1] class [System.Runtime]System.IFormattable formattable, [2] valuetype Program.Color V_2 ) // [28 9 - 28 10] IL_0000: nop // [29 13 - 29 39] IL_0001: ldloca.s color IL_0003: initobj Program.Color // [30 13 - 30 46] IL_0009: ldloc.0 // color IL_000a: box Program.Color IL_000f: stloc.1 // formattable // [31 13 - 31 39] IL_0010: ldloc.1 // formattable IL_0011: unbox.any Program.Color IL_0016: stloc.2 // V_2 IL_0017: br.s IL_0019 // [32 9 - 32 10] IL_0019: ldloc.2 // V_2 IL_001a: ret } // end of method Program::Test123
原因解析
这是.NET JIT编译器的栈上装箱优化导致的:
- IL中的
box指令是静态编译阶段生成的,但JIT编译时会分析代码的逃逸情况:如果装箱后的对象仅在当前方法内使用,没有传递到方法外部(即没有逃逸),JIT会将装箱对象分配在栈内存而非堆内存。 - 栈内存的分配和释放由方法调用栈自动处理,不需要GC参与,而
MemoryDiagnoser默认只统计堆内存分配,因此不会显示额外的内存分配。 - 对于
int这类原生值类型,JIT还有更特殊的优化(比如复用内部的缓存或直接消除装箱),同样不会产生堆分配。
如果修改代码让装箱后的接口对象逃逸出当前方法(比如作为返回值或传递给其他方法),JIT就无法进行栈上分配优化,此时会产生明显的堆内存分配,MemoryDiagnoser就能检测到。
内容的提问来源于stack exchange,提问作者ZinSunrise
相关产品推荐
相关产品推荐

