今回のケースは次の通りです。
- クラス1つにつき1回のデータが入っていて、データ(メンバ変数)数は500。
イメージとしては1クラス/1レコードといった具合です。
但しそのデータはクラス内クラスによって区分されて保存されている。
たとえば、
class a{
class aa{
public float val1;
class aaa{
public int val2;
}
...
class az{
public int val99;
}
のように。こういう具合で細分化された項目数の合計が500あります。 - そのレコードが数万程度リストとして保持されている。
- そのリストを、メンバ名で(イメージとしてはカラム名で)で横ぐしで集計したい。
クラス設計が悪い?
まったくその通りですが、文句を言っても解決にならんので、とりあえずなんか考えてみたいと思います。
いろいろ手法があると思います。クラスの構造をのべたんに開いて、ってこりゃ方言かな?、構造を展開してしまうとか別にリストを作り直すとか。
ここでは、あえて強引にメンバ名で当該クラスにアクセスしてデータを取得するという手法をとってみることにすることにします。イメージとしてはXPath式でデータにアクセスするような感じで。
当然、この場合、考えつくのはリフレクションを用いることです。
そして、まさしく当然のごとくアクセスできます。便利です。
ですが、リフレクションは遲いというのがもっぱらの評価です。
そこで、今回の比較と相成ります。
Case1. switch( メンバ名 ) の場合。
public float get(string member)
{
switch ( member )
{
case "aa.val1":
return aa.val1;
のような延々と500ものcase文が続く壮絶な関数を作って時間を計測してみました。(時間はあとでまとめて書きます。)
Case2. Dictionaryを用いた場合。
dic = new Dictionary<string, float>() {といった具合に、データを保持しているクラスがデータを収集し終えた段階で辞書をこさえておいて、アクセスする場合にはその辞書に対してキーとしてメンバ名を渡して値を得る方法です。
{ "aa.val1", aa.val1 },
Case3. リフレクションを用いた場合。
単純にInvokeMemberメソッドでGetFieldする方法です。
上記の3例でかかった時間をStopwatchクラスで計測しました。(Core i7/たしか4770)
- クラスから1回全要素を取得するループの実行時間
switchの場合: 50ms
Dictionaryの場合: 12ms
Reflectionの場合: 1ms - 10回全要素を取得するループの実行時間
switchの場合: 83ms
Dictionaryの場合: 11ms
Reflectionの場合: 9ms - 100 回全要素を取得するループの実行時間
switchの場合: 137ms
Dictionaryの場合: 15ms
Reflectionの場合: 94ms - 10000回全要素を取得するループの実行時間
switchの場合: 1072ms
Dictionaryの場合: 379ms
Reflectionの場合: 7938ms
10回程度のループ処理ならInvokeMemberで値を取得したほうが早かったんです。
逆コンパイルしたらswitch文のコードは文字列のハッシュをとったりして涙ぐましい最適化処理をコンパイラがやってくれていました。
しかし、辞書とリフレクションを用いた関数を利用して取得したほうは書いたままのコードでした。
私はGCがあるような言語は業務で使いたくないので詳しくないのですが、今回のような結果が出るとは思いにもよりませんでした。
面白い!実に奥が深い!!
なぜ回数が少ないとInvokeMemberが早いのか。
今回は考察まで踏み込めませんでした。ごめんなさい。
でも、こんなことがあるから後期中齢者になってもプログラミングがやめられません。
0 件のコメント:
コメントを投稿