Search code examples
c#structcilboxing

Why is there no extra memory allocation


When I was conducting the following benchmark test, the benchmark information indicated that there was no additional memory allocation. However, doesn't converting a struct to an interface involve boxing?

        [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);
        }
    }

.NET 8

result

The Color struct consists of only four double members: RGBA.

"I tried to open Rider to analyze the IL code and found that there are indeed boxing and unboxing instructions. I would like to understand why there is no additional memory allocation."

  .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

Solution

  • The JIT compiler is smart enough to remove the formattable gymnastics and essentially return just the color

    If you change the method to actually return IFormattable, you will have allocations:

    public IFormattable Test123() {
        Color color = new Color();
        IFormattable formattable = color;
        return formattable;
    }