0

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

我想知道的是装箱和拆箱是如何工作,最好有证据。我的意思是:

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

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

4

1 回答 1

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

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

  • 由于值类型和类类型都派生自对象,并且装箱值应该是类类型,我想知道装箱值类型的 vtable 查找是否不同于值类型的 vtable 查找?

情况似乎是这样:对值类型的 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?实现为值类型而不是框?

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

  1. 性能和内存大小。int 的开销?是 4 个字节,而装箱值的开销在 x86 上是 4 个字节,在 x64 上是 8 个字节。这意味着int?作为对象复制更快或同样快。通过方法复制时的开销是相同的。同样使用值类型的 vtable 查找要快得多。
  2. 兼容性。装箱对象的类型 = 未装箱对象的类型。对于整数?你想要一个不同的类型而不破坏与旧版本代码的兼容性。改变int?反对需要语言支持并打破依赖这些类型的旧版本相同。

结论:装箱确实总是将值类型复制到堆中,它只是一个普通对象。您可能注意到的唯一奇怪的事情是对象中的类型引用是对未装箱值的原始(值)类型的类型引用。我找不到任何证据表明装箱值不是存在于堆上的“普通类类型”对象。

于 2013-01-21T14:08:56.937 回答