Why does calling an explicit interface implementation on a value type cause it to be boxed?

前端 未结 3 1706
日久生厌
日久生厌 2020-12-28 18:43

My question is somewhat related to this one: How does a generic constraint prevent boxing of a value type with an implicitly implemented interface?, but different because it

3条回答
  •  旧巷少年郎
    2020-12-28 19:20

    The value doesn't necessarily get boxed. The C#-to-MSIL translation step usually doesn't do most of the cool optimizations (for a few reasons, at least some of which are really good ones), so you'll likely still see the box instruction if you look at the MSIL, but the JIT can sometimes legally elide the actual allocation if it detects that it can get away with it. As of .NET Fat 4.7.1, it looks like the developers never invested in teaching the JIT how to figure out when this was legal. .NET Core 2.1's JIT does this (not sure when it was added, I just know that it works in 2.1).

    Here are the results from a benchmark I ran to prove it:

    BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134
    Intel Core i7-6850K CPU 3.60GHz (Skylake), 1 CPU, 12 logical and 6 physical cores
    Frequency=3515626 Hz, Resolution=284.4444 ns, Timer=TSC
    .NET Core SDK=2.1.302
      [Host] : .NET Core 2.1.2 (CoreCLR 4.6.26628.05, CoreFX 4.6.26629.01), 64bit RyuJIT
      Clr    : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3131.0
      Core   : .NET Core 2.1.2 (CoreCLR 4.6.26628.05, CoreFX 4.6.26629.01), 64bit RyuJIT
    
    
                    Method |  Job | Runtime |     Mean |     Error |    StdDev |  Gen 0 | Allocated |
    ---------------------- |----- |-------- |---------:|----------:|----------:|-------:|----------:|
           ViaExplicitCast |  Clr |     Clr | 5.139 us | 0.0116 us | 0.0109 us | 3.8071 |   24000 B |
     ViaConstrainedGeneric |  Clr |     Clr | 2.635 us | 0.0034 us | 0.0028 us |      - |       0 B |
           ViaExplicitCast | Core |    Core | 1.681 us | 0.0095 us | 0.0084 us |      - |       0 B |
     ViaConstrainedGeneric | Core |    Core | 2.635 us | 0.0034 us | 0.0027 us |      - |       0 B |
    

    Benchmark source code:

    using System.Runtime.CompilerServices;
    using BenchmarkDotNet.Attributes;
    using BenchmarkDotNet.Attributes.Exporters;
    using BenchmarkDotNet.Attributes.Jobs;
    using BenchmarkDotNet.Running;
    
    [MemoryDiagnoser, ClrJob, CoreJob, MarkdownExporterAttribute.StackOverflow]
    public class Program
    {
        public static void Main() => BenchmarkRunner.Run();
    
        [Benchmark]
        public int ViaExplicitCast()
        {
            int sum = 0;
            for (int i = 0; i < 1000; i++)
            {
                sum += ((IValGetter)new ValGetter(i)).GetVal();
            }
    
            return sum;
        }
    
        [Benchmark]
        public int ViaConstrainedGeneric()
        {
            int sum = 0;
            for (int i = 0; i < 1000; i++)
            {
                sum += GetVal(new ValGetter(i));
            }
    
            return sum;
        }
    
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static int GetVal(T val) where T : IValGetter => val.GetVal();
    
        public interface IValGetter { int GetVal(); }
    
        public struct ValGetter : IValGetter
        {
            public int _val;
    
            public ValGetter(int val) => _val = val;
    
            [MethodImpl(MethodImplOptions.NoInlining)]
            int IValGetter.GetVal() => _val;
        }
    }
    

提交回复
热议问题