2006年10月14日
川俣晶の縁側ソフトウェア技術雑記total 14106 count

時代錯誤に今更こんなことを検証するのかよ! C#で何バイトなら構造体にして許容できるか?

Written By: 川俣 晶連絡先

 C#で書いていて、繰り返し実行されるコードでnewするのはイヤだな~と思うわけですよ。

 ヒープから小さなオブジェクトをたくさん取ってくるとオーバーヘッドがでかいから。

 かといって、構造体にすると、引数や戻り値で移動するだけで丸ごとコピーになってオーバーヘッドがでかいわけです。

 いったい何バイトなら構造体にして許容なのだろう……。

 なんてことを今頃考えているなんて時代錯誤です (汗。

方針 §

 変な最適化が入ると困るので、別のプロジェクトのメソッド呼び出しの引数と戻り値に様々なサイズの構造体を渡して処理時間を見ることにしました。

環境 §

 Pentium D 3.2GHz+メモリ 2GB

 Windows XP SP2

 C# 2.0+.NET Framework 2.0

 Viusal Studio 2005上でデバッグ実行

検証プログラム (呼び出され側、クラスライブラリ) §

using System;

using System.Collections.Generic;

using System.Text;

namespace ClassLibrary1

{

    public class c4

    {

        public int test1;

    }

    public class c16

    {

        public int test1;

        public int test2;

        public int test3;

        public int test4;

    }

    public class c64

    {

        public int test11, test12, test13, test14;

        public int test21, test22, test23, test24;

        public int test31, test32, test33, test34;

        public int test41, test42, test43, test44;

    }

    public struct s4

    {

        public int test1;

    }

    public struct s16

    {

        public int test1;

        public int test2;

        public int test3;

        public int test4;

    }

    public struct s64

    {

        public int test11, test12, test13, test14;

        public int test21, test22, test23, test24;

        public int test31, test32, test33, test34;

        public int test41, test42, test43, test44;

    }

    public class passClass

    {

        public static c4 pass( c4 t ) { return t; }

        public static c16 pass(c16 t) { return t; }

        public static c64 pass(c64 t) { return t; }

        public static s4 pass(s4 t) { return t; }

        public static s16 pass(s16 t) { return t; }

        public static s64 pass(s64 t) { return t; }

    }

}

検証プログラム (呼び出す側、コンソールアプリケーション) §

using System;

using System.Collections.Generic;

using System.Text;

using ClassLibrary1;

namespace ConsoleApplication89

{

    class Program

    {

        private const int MAX_COUNT = 100000000;

        static void Main(string[] args)

        {

            DateTime start1 = DateTime.Now;

            for (int i = 0; i < MAX_COUNT; i++)

            {

                c4 t = passClass.pass( new c4() );

            }

            DateTime end1 = DateTime.Now;

            Console.WriteLine(end1 - start1);

            DateTime start1a = DateTime.Now;

            for (int i = 0; i < MAX_COUNT; i++)

            {

                c16 t = passClass.pass(new c16());

            }

            DateTime end1a = DateTime.Now;

            Console.WriteLine(end1a - start1a);

            DateTime start1b = DateTime.Now;

            for (int i = 0; i < MAX_COUNT; i++)

            {

                c64 t = passClass.pass(new c64());

            }

            DateTime end1b = DateTime.Now;

            Console.WriteLine(end1b - start1b);

            DateTime start2 = DateTime.Now;

            for (int i = 0; i < MAX_COUNT; i++)

            {

                s4 t = passClass.pass(new s4());

            }

            DateTime end2 = DateTime.Now;

            Console.WriteLine(end2 - start2);

            DateTime start3 = DateTime.Now;

            for (int i = 0; i < MAX_COUNT; i++)

            {

                s16 t = passClass.pass(new s16());

            }

            DateTime end3 = DateTime.Now;

            Console.WriteLine(end3 - start3);

            DateTime start4 = DateTime.Now;

            for (int i = 0; i < MAX_COUNT; i++)

            {

                s64 t = passClass.pass(new s64());

            }

            DateTime end4 = DateTime.Now;

            Console.WriteLine(end4 - start4);

        }

    }

}

結果 §

種類タイム
クラス4バイト00:00:02.9531439
クラス16バイト00:00:03.7343989
クラス64バイト00:00:06.1875396
構造体4バイト00:00:01.1718825
構造体16バイト00:00:04.3437778
構造体64バイト00:00:14.2344661

 クラスは、サイズによって引き渡しに要する時間に差はないと考えられますが、newのオーバーヘッドがサイズで変わるので3種類用意しました。

 クラス、構造体のバイト数は、もしかしたら正しくないかもしれません。

 ちなみに、newのオーバーヘッドは生成時の1回きりですが、引数や戻り値に渡すオーバーヘッドは、渡す回数だけ発生します。その点で、構造体は不利になることに注意が必要です。

考察 §

 クラスはnewによってヒープからメモリを確保していると考えられます。

 一方、構造体の引数と戻り値はスタック上に確保され、動的なメモリ確保は発生していないと考えられます。

 つまり、上記の数字は、クラスの場合のみメモリの確保というオーバーヘッドが含まれていると考えられます。

 さて、これを見て分かるのは、サイズごとに有利不利が変わっている状況です。

 4バイトのデータを扱う場合はオーバーヘッドが大きく響くためか、構造体を使う方が有利となります。

 しかし、16バイトではその差は小さくなり、僅かに構造体が不利となります。

 そして、64バイトでは圧倒的に構造体が不利になります。

 この問題を解決するためには、構造体を参照渡しでメソッドに渡す方法があり得ますが、単に速度を稼ぐために参照渡しにするぐらいなら、おとなしくクラスにしておく方が良いような気もします。

更に考察 §

 構造体の損益分岐点が16バイトあたりに存在する……という結論は、確実なものではありません。環境や使い方によって変わりうるものです。

 しかし、仮に16バイトだとすると、rectangleF構造体の存在意義が何となく見えてきます。

 float4つの数値から構成されるrectangleF構造体のサイズは16バイトです。

 整数4つの数値から構成されるrectangle構造体のサイズも同じく16バイトです。

 しかし、double4つの数値から構成されるrectangleD構造体はありません。それを作るとすればサイズは32バイトになりますが、これは構造体で扱うには効率の良くないサイズということになります。

 つまり、rectangleF構造体は、あくまでfloat4つの16バイトの構造体として作られる必然性があったと言うことです。

 ……という話が事実かどうかは分からないので、眉に唾を付けるように!

Facebook

キーワード【 川俣晶の縁側ソフトウェア技術雑記
【技術雑記】の次のコンテンツ
2006年
10月
15日
メソッドが匿名であることの決定的な価値とは
3days 0 count
total 2045 count
【技術雑記】の前のコンテンツ
2006年
10月
13日
C#による拡張可能なライブラリの設計指針の私的試行メモ
3days 0 count
total 4440 count

このコンテンツを書いた川俣 晶へメッセージを送る

[メッセージ送信フォームを利用する]

メッセージ送信フォームを利用することで、川俣 晶に対してメッセージを送ることができます。

この機能は、100%確実に川俣 晶へメッセージを伝達するものではなく、また、確実に川俣 晶よりの返事を得られるものではないことにご注意ください。

このコンテンツへトラックバックするためのURL

http://mag.autumn.org/tb.aspx/20061014194823
サイトの表紙【技術雑記】の表紙【技術雑記】のコンテンツ全リスト 【技術雑記】の入手全リスト 【技術雑記】のRSS1.0形式の情報このサイトの全キーワードリスト 印刷用ページ

管理者: 川俣 晶連絡先

Powered by MagSite2 Version 0.34 (Alpha-Test) Copyright (c) 2004-2018 Pie Dey.Co.,Ltd.