おーみんブログ

C#, ASP.NET Core, Unityが大好きです。

ボクシング(ボックス化)のパフォーマンスを検証してみた。

はじめに

以前転職活動をしたときに面接官(たぶん現役のバリバリの技術屋さん)に「ボクシングって知っていますか?」と聞かれてスポーツのボクシングしか頭に浮かばなかったことを思い出したのでお勉強してみました。(/・ω・)/ (本来ならばその場で調べるべきではあるのですが...)

ボクシング(ボックス化)とは?

ボクシング(ボックス化)とは、値型をobject型などの参照型に代入した際に値型から参照型へ変換する処理のことを指します。

どうやらボクシングは重い処理らしい

プログラムでは変数等を一時的にメモリで管理し、値型はスタックと呼ばれる領域で、参照型はヒープと呼ばれる領域で管理を行います。 ヒープ領域の確保はスタック領域にある値をヒープ領域へコピーし、アドレスや型情報をスタック領域へ格納するという段階を踏むためにスタック領域の確保と比較すると重たい処理となります。 (以下の2つの記事がとても分かりやすかったです。ありがとうございます!)

ufcpp.net

blog.goo.ne.jp

実際に検証してみる

どれくらい速度が変わるのか試してみました。 以下はobject型(参照型)へint型の値を代入する処理とint型(値型)へint型の値を代入する処理を比較しています。 前者ではボクシング(ボックス化)が発生しています。

処理時間計測は以下の記事を参考にさせていただきました。ありがとうございました!

qiita.com

サンプルコード

static void Main(string[] args)
{
    object a;
    int b;

    var sw = new System.Diagnostics.Stopwatch();
    sw.Start();
    //20億回ボクシングが発生
    for(var i = 0; i < 2000000000; i++)
    {
        a = 5;
    }
    sw.Stop();
    TimeSpan ts = sw.Elapsed;
    Console.WriteLine($" {ts.Hours}時間 {ts.Minutes}分 {ts.Seconds}秒 {ts.Milliseconds}ミリ秒");

    var sw2 = new System.Diagnostics.Stopwatch();
    sw2.Start();
    //こちらはそのままint型へ値型を代入
    for (var i = 0; i < 2000000000; i++)
    {
        b = 5;
    }
    sw2.Stop();
    TimeSpan ts2 = sw2.Elapsed;
    Console.WriteLine($" {ts2.Hours}時間 {ts2.Minutes}分 {ts2.Seconds}秒 {ts2.Milliseconds}ミリ秒");
}
0時間 0分 9秒 664ミリ秒   <-- ボクシングが発生していた部分
0時間 0分 3秒 334ミリ秒   <-- ボクシングは発生していない部分

お、おお~~。 約3倍くらいですかね。

確かにボクシングが重い処理であることが分かりました。

おわりに

とはいったものの、原理はたぶん分かったのですが。。。 むむむ。。。あれれ?日常のどこでこれを意識するのだ。。。となったので次はその段階へチャレンジしたいと思います。 分かったら追記するぞ~!!!

【2021年8月11日追記】

お客様先の上司に教えてもらいました~!!感謝!! どうやらボックス化はC#1.0~C#2.0の頃に話題になったようですね~。 例えばSystem.Collections.ArrayListでは格納できるものがObject型だったため、参照型の時は問題ないのですがintなどの値型を入れる際にボックス化が発生してパフォーマンスに影響が出てしまうようでした。 その影響を解消するために出てきたのがジェネリック版であるListだったのですね(System.Collections.Generic)。

ボックス化は今ではだいぶ解消されていますがEqualsなどの修正がされていない(修正が困難)ものもあるのでそれを使うときはボクシングを意識する必要がありそうです。