拳击和拆箱如何在最低级别工作

时间:2013-01-18 08:42:06

标签: .net memory-management runtime boxing

首先让我告诉我,我知道MSDN on boxing and unboxing的内容,并在拳击和拆箱上看到关于SO的帖子。我也理解为什么拳击是有用的,它在高水平上做了什么,并且曾多次与IL合作过......所以请不要拖延。

我想知道的是拳击和拆箱如何完全,最好是证明。我的意思是:

  • 运行时是否真的为每次装箱/拆箱操作复制堆上的数据,还是使用引用计数等技巧?
  • 使用std收集堆垃圾上的盒装值。垃圾收集器还是在一块特殊的记忆中?
  • 或者更一般:堆上的盒装值是否适用不同的规则? (因为我可以理解为什么会出现这种情况)
  • 运行时IL是否在内联代码时优化装箱/拆箱操作,或者这是不可能的?如果可能的话,你能帮助'JIT编译一下吗?
  • 似乎盒装值包含一个类型;盒装值的数据结构(或:开销)是多少/多少? “内部”是什么样的?
  • 由于值类型和类类型都是从对象派生的,并且因为盒装值应该是类类型,我想知道盒装值类型的vtable查找是否与值类型的vtable查找不同? / LI>
  • 为什么'int?'实现为值类型而不是框?

换句话说,我读过的帖子谈到“运行时实现细节”,这正是我想知道的: - )

1 个答案:

答案 0 :(得分:0)

最后我觉得我弄清楚了......以下是我找到的答案。如果我犯了错误,请告诉我。

  • 运行时是否真的为每次装箱/拆箱操作复制堆上的数据,还是使用引用计数等技巧?

    public void Test6()
    {
        GC.Collect(GC.MaxGeneration);
        GC.WaitForFullGCComplete();
    
        object[] myarr = new object[1024 * 1024];
    
        long mem1 = GC.GetTotalMemory(true);
    
        int a = 1;
        for (int i = 0; i < myarr.Length; ++i)
        {
            myarr[i] = a;
        }
    
        long mem2 = GC.GetTotalMemory(true);
    
        Console.WriteLine("Total memory usage is {0} bytes", mem2 - mem1);
    
        // Make sure we use it so that the JIT doesn't optimize our code
        int sum = 0;
        for (int i = 0; i < myarr.Length; ++i)
        {
            sum += (int)myarr[i];
        }
        Console.WriteLine("Sum = {0}", sum);
    }
    

对于x86,结果是12582912 - 这是完整对象的行为:4x1M int,4x1M类型引用和4x1M指针存储在数组中。答:它只是复制到堆中。

这使得运行时不太可能使用不同的规则IMO。

  • 运行时IL是否在内联代码时优化装箱/拆箱操作,或者这是不可能的?如果可能的话,你能帮助'JIT编译一下吗?

似乎没有。尝试:

    private object IntBox1()
    {
        return 1;
    }

    private int IntNotBox1()
    {
        return 1;
    }

    public int Total1()
    {
        int sum = 0;
        for (int i = 0; i < 100000000; ++i)
        {
            sum += (int)IntBox1();
        }
        return sum;
    }
    public int Total2()
    {
        int sum = 0;
        for (int i = 0; i < 100000000; ++i)
        {
            sum += IntNotBox1();
        }
        return sum;
    }

时间上有显着差异,所以没有。我还没有找到一种方法来帮助运行时优化装箱/拆箱。如果有人找到任何方法让运行时优化盒子/ unbox操作,请分享。

  • 由于值类型和类类型都是从对象派生的,并且因为盒装值应该是类类型,我想知道盒装值类型的vtable查找是否与值类型的vtable查找不同? / LI>

似乎是这种情况:对值类型的vtable查找要快得多。

    public void Test4()
    {
        int a = 1;
        object oa = a;

        Stopwatch sw = new Stopwatch();
        sw.Start();
        int sum = 0;
        for (int i = 0; i < 100000000; ++i)
        {
            sum += a.GetHashCode();
        }
        Console.WriteLine("Calc {0} took {1:0.000}s", sum, new TimeSpan(sw.ElapsedTicks).TotalSeconds);

        sw = new Stopwatch();
        sw.Start();
        sum = 0;
        for (int i = 0; i < 100000000; ++i)
        {
            sum += oa.GetHashCode();
        }
        Console.WriteLine("Calc {0} took {1:0.000}s", sum, new TimeSpan(sw.ElapsedTicks).TotalSeconds);
    }
  • 为什么'int?'实现为值类型而不是框?

我现在认为这与2个原因有关。

  1. 性能和内存大小。 int的开销?是4个字节,而盒装值的开销在x86上是4个字节,在x64上是8个字节。这意味着int?复制为对象更快或同样快。通过方法复制时的开销是相同的。使用值类型的vtable查找也要快得多。
  2. 兼容性。盒装对象的类型=未装箱对象的类型。对于int?你想要一个不同的类型而不破坏与旧版本代码的兼容性。改变int?对象需要语言支持并且打破依赖这些类型的旧版本。
  3. 结论:装箱真的总是将值类型复制到堆中,它只是一个普通的对象。您可能注意到的唯一奇怪的事情是对象中的类型引用是对未装箱值的原始(值)类型的类型引用。我找不到任何证据表明盒装值不是生活在堆上的“普通类型”对象。