まあ、そりゃそうだよね、というものばかりです(以下の記事で使ったデータを出したプログラムは文末に付録としてご覧いただけます)。
1.インスタンスの生成時間が異常に遅い
例えば1単位当たり5個のint型で表せる大量のデータのリストが欲しくなったとします。
その場合、1単位を表すには以下のケースが考えられます。
case1:5つのデータを持つクラス
1 2 3 4 5 6 7 | class TestObject1 { public int data1; public int data2; public int data3; public int data4; public int data5; } |
1 2 3 4 | class TestObject2 { public int [] datas = new int [5]; } |
1 | int [] datas = new int [ 5 ]; |
1 2 3 4 5 6 7 8 | struct TestStruct1 { public int data1; public int data2; public int data3; public int data4; public int data5; } |
こんな単純なクラスなのに、これほど劇的な違いが生まれてしまいます。
ケース3のList<int[]>ですが、「ふーん、int[]のAddのほうがただのクラスより遲いんだねー、使うのやめとこ」と判断する前に、そもそもこの使い方は誤っています。
実際には5バイトのint[]をリストに追加しまくるのは無駄です。こうするべきです。
Case3A: int[] array = new int[ 5*追加要素数 ];
すると、インスタンス生成に係る回数は1回、必要な時間はほぼゼロ。
上限数が分かっていれば、速度だけで言えばこれ以外の選択肢はありません。
但し、デメリットもまた多いです。
まず、配列だけ見てもなにを表しているのかパッと見わかりません。データの取り扱い方法は工夫する必要が出てくるでしょう。
また、処理中に追加要素数が上限を超えたなら、当然arrayを自力で拡張する必要があります。
素直にCで組んだほうがいいと思います。
また、ケース2は最悪です。自インスタンス生成時間にメンバのint[5]のインスタンス生成時間が加わるため、見事に実行時間がケース1の倍になっています。
ケース4の構造体の早さは圧倒的です。おまけにデータ加工用のメンバ関数などのオブジェクト指向の恩恵も得られるので、わざわざC#を使うならこれを採用したいところです。
自分の場合は、今回はケース1からケース3Aに変更しました。その時の劇的な処理速度向上への驚きがこの記事を書いたきっかけです。おハズカシイ。
2.Math.Ceilingを使ってもそこまで遲くない
私は古い世代なので、浮動小数演算を避けたくなってしまう癖があるのですが、念のため計測してみました。
確かに、ほんのわずかに速いものの、ほとんど変わりません。
但し、上記結果はReleaseビルドでの結果です。Debugビルドでの結果はインライン化がされない等の影響からか整数版CEILは致命的に遲くなります。
素直にMathライブラリを使ったほうがいいですね。
見る人が見たら何言ってんだ、と言われるようなことばかりなのでしょうが、恥をさらす当ブログの趣旨によりここに記録する次第です。
普段使うことがないので、勉強になりました。
ふろく:上記図表の計測プログラム
見る人が見たら何言ってんだ、と言われるようなことばかりなのでしょうが、恥をさらす当ブログの趣旨によりここに記録する次第です。
普段使うことがないので、勉強になりました。
ふろく:上記図表の計測プログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | class Program { static void Main( string[] args ) { test test = new test(); test.run( 5000000 ); test.run( 10000000 ); test.run( 20000000 ); Console.WriteLine( "Hit ANY KEY" ); Console.ReadKey(); } } class test { class TestObject1 { public int data1; public int data2; public int data3; public int data4; public int data5; } class TestObject2 { public int [] datas = new int [5]; } struct TestStruct1 { public int data1; public int data2; public int data3; public int data4; public int data5; } List<TestObject1> list1 = new List<TestObject1>(); List<TestObject2> list2 = new List<TestObject2>(); List< int []> list3 = new List< int []>(); List<TestStruct1> list4 = new List<TestStruct1>(); Stopwatch sw = new Stopwatch(); void Elapse() { Console.WriteLine( $ " {sw.ElapsedMilliseconds}ミリ秒" ); } void case1( int cnt ) { for ( int i=0;i< cnt; i++ ) { TestObject1 o = new TestObject1(); o.data1 = i; o.data2 = i; o.data3 = i; o.data4 = i; o.data5 = i; list1.Add( o ); } } void case2( int cnt ) { for ( int i = 0; i < cnt; i++ ) { TestObject2 o = new TestObject2(); o.datas[ 0 ] = i; o.datas[ 1 ] = i; o.datas[ 2 ] = i; o.datas[ 3 ] = i; o.datas[ 4 ] = i; list2.Add( o ); } } void case3( int cnt ) { for ( int i = 0; i < cnt; i++ ) { int [] datas = new int [ 5 ]; datas[ 0 ] = i; datas[ 1 ] = i; datas[ 2 ] = i; datas[ 3 ] = i; datas[ 4 ] = i; list3.Add( datas ); } } void case4( int cnt ) { for ( int i = 0; i < cnt; i++ ) { TestStruct1 o; o.data1 = i; o.data2 = i; o.data3 = i; o.data4 = i; o.data5 = i; list4.Add( o ); } } void case5( int cnt ) { for ( int i = 0; i < cnt; i++ ) { TestStruct1 o; o.data1 = i; o.data2 = i; o.data3 = i; o.data4 = i; o.data5 = i; list4.Add( o ); } } int CEIL( int val, int mul10 ) { int ret = val * mul10; if ( ret % 10 != 0 ) { ret += 10; } return ret / 10; } void mathCase1( int cnt) { for ( int i = 0; i < cnt; i++ ) { TestStruct1 o; o.data1 = i; o.data2 = CEIL( i, 15 ); o.data3 = CEIL( i, 16 ); o.data4 = i * 2; o.data5 = CEIL( i , 22 ); } } void mathCase2( int cnt ) { for ( int i = 0; i < cnt; i++ ) { TestStruct1 o; o.data1 = i; o.data2 = ( int )Math.Ceiling( i * 1.5 ); o.data3 = ( int )Math.Ceiling( i * 1.6 ); o.data4 = i * 2; o.data5 = ( int )Math.Ceiling( i * 2.2 ); } } void interval() { int WAITSEC = 5; GC.Collect(); Console.WriteLine( $ "Wait for {WAITSEC}seconds..." ); Thread.Sleep( WAITSEC * 1000 ); } delegate void cases( int cnt); void job(string titile, int cnt, cases c, Object listobj) { int LOOPCNT = 10; Stopwatch stopwatch = new Stopwatch(); MethodInfo mi = null; if ( listobj != null ) { Type t = listobj.GetType(); mi = t.GetMethod( "Clear" ); t.GetProperty( "Capacity" ).SetValue( listobj, cnt, null ); } stopwatch.Start(); for ( int i = 0; i < LOOPCNT; i++ ) { c( cnt ); if ( listobj != null ) { mi.Invoke( listobj, null ); } } stopwatch.Stop(); Console.WriteLine( $ "{titile}:{LOOPCNT}回平均値: {stopwatch.ElapsedMilliseconds/ LOOPCNT}ms" ); } public void run( int loopcnt) { Console.WriteLine( $ "追加要素数:{loopcnt}" ); job( "Case1" , loopcnt, case1, list1 ); job( "Case2" , loopcnt, case2, list2 ); job( "Case3" , loopcnt, case3, list3 ); job( "Case4" , loopcnt, case4, list4 ); job( "整数版CEIL" , loopcnt, mathCase1, null ); job( "Math.Ceiling" , loopcnt, mathCase2, null ); interval(); } } |
0 件のコメント:
コメントを投稿