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」なのですか?ボックスではなく値型として実装されていますか?

これには2つの理由が関係していると今では考えています。

  1. パフォーマンスとメモリ サイズ。intのオーバーヘッド?は 4 バイトですが、ボックス化された値のオーバーヘッドは x86 では 4 バイト、x64 では 8 バイトです。つまりint?オブジェクトとしてコピーする方が高速または同等に高速です。メソッドを介してコピーするときのオーバーヘッドは同じです。また、値型の vtable ルックアップを使用すると、はるかに高速になります。
  2. 互換性。ボックス化されたオブジェクトのタイプ = ボックス化されていないオブジェクトのタイプ。イント用?古いバージョンのコードとの互換性を損なうことなく、別の型が必要です。intを変更しますか?to オブジェクトには言語サポートが必要であり、これらの型が同じであることに依存する古いバージョンが壊れます。

結論:ボクシングは実際には常に値の型をヒープにコピーし、それは単なる通常のオブジェクトです。唯一奇妙な点は、オブジェクト内の型参照が、ボックス化されていない値の元の (値) 型への型参照であることです。ボックス化された値がヒープ上に存在する「通常のクラス型」オブジェクトではないことを示唆する証拠を見つけることができません。

于 2013-01-21T14:08:56.937 に答える