You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 21:35:07