最低级别的装箱和拆箱如何工作 [英] How does boxing and unboxing work at the lowest level

查看:59
本文介绍了最低级别的装箱和拆箱如何工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Let me start by telling that I know the contents of MSDN on boxing and unboxing and seen the post on SO on boxing and unboxing. I also understand why boxing is useful, what it does on a high level and have worked with IL on many occasions... so please don't hold back.

我想知道装箱和拆箱如何完全,最好有证据.我的意思是:

What I would like to know is how boxing and unboxing works exactly, preferably with proof. What I mean with that is:

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

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

In other words, where the posts I've read talk about "runtime implementation details", that's exactly what I want to know :-)

推荐答案

最后我想我明白了...这里是我找到的答案.如果我犯了错误,请告诉我.

At last I think I figured it out... Here are the answers that I found. If I made errors, please let me know.

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

  • Does the runtime really copy the data on the heap for every boxing/unboxing operation or does it use tricks like reference counting?

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 指针存储在数组中.答:它只是复制到堆中.

The result of this is 12582912 for x86 - which is the behaviour of full-blown objects: 4x1M int's, 4x1M type references and 4x1M pointers stored in the array. Answer: it just copies to the heap.

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

That makes it very unlikely that the runtime uses different rules 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;
    }

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

There's a notable difference in time, so it doesn't. I haven't found a way to help the runtime optimizing the boxing/unboxing. If anyone has found any way to make the runtime optimize the box/unbox operation away, please share it.

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

情况似乎是这样:值类型上的 vtable 查找要快得多.

This appears to be the case: vtable lookups on a value type are much faster.

    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"?实现为值类型而不是框?
  • 我现在认为这与两个原因有关.

    I now think this has to do with 2 reasons.

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

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

    Concluding: boxing really always copies the value type to the heap and it's just a normal object. The only strange thing you might notice is that the type reference in the object is a type reference to the original (value) type of the unboxed value. I cannot find any evidence suggesting a boxed value is not a "normal class-type" object that lives on the heap.

    这篇关于最低级别的装箱和拆箱如何工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆