院長のメモ帖
2015年10月13日 火曜日
IComparer<T>のCompareメソッド
前回、J2のリーグシミュレーションをおこないましたが、そのプログラムをつくっていてが付いたことがあります。
パフォーマンス解析ツールがVisual Studio Ultimateじゃなくても使えるようになったので、モンテカルロシミュレーターをC#6.0で書き直したついでにパフォーマンス測定してみたところ、乱数発生系と順位並べ替えがホットスポットになっていました。まあ、この両者がシミュレーターのキモの部分ですから当たり前なんですが、とりあえず早くできないものかと並べ替え系を検討してみました。
Github
class Main
{
List<TeamResult> Teams;
void Order()
{
Teams = Teams.OrderBy(r => r, new リーグ順位Comparer()).ToList();
}
}
class リーグ順位Comparer : Comparer<TeamResult>
{
static int count1, count2, count3;
public override int Compare(TeamResult x, TeamResult y)
{
count1++;
int ret = 勝ち点得失点差で判定(x, y);
if (ret != 0)
return ret;
count2++;
ret = 当該チームの成績で判定(x, y);
if (ret != 0)
return ret;
count3++;
return 抽選(x, y);
}
}
count1, count2, count3の数を調べたところ、count2とcount3の値が非常に近い値になっていました。抽選になるケースって実際のリーグではほぼ発生しない事象ですので、この二つの変数の値はかなり大きな差が付かねばなりません。当該チームの成績で判定()にバグがあるかと検討しましたがとくにない。はっときがついて、Compareメソッドの先頭に
Github
public override int Compare(TeamResult x, TeamResult y)
{
if (x == y)
{
count0++;
return 0;
}
count1++;
int ret = 勝ち点得失点差で判定(x, y);
if (ret != 0)
return ret;
.
.
.
参照同値性の判定ルーチンを加えたとことろ、count0の値が相当な数になりました。LINQのOrderByメソッドがどんなソートロジックを使っているか知りませんが、ユニークな要素しかない列挙で同じオブジェクトをCompareしているとは予想していませんでした。ICompare<T>の解説ではたいていいきなり内容の比較から入っているサンプルばっかりでしたので、そういうものだと思ってリスト1の書き方をしていたのですが、値型ならいざしらず、参照型の場合は真面目に参照同値性とnullチェックを入れないとだめなのですね。というわけで最終的には
Github
public override int Compare(TeamResult x, TeamResult y)
{
if (ReferenceEquals(x, y))
return 0;
else if (ReferenceEquals(x, null))
return -1;
else if (ReferenceEquals(y, null))
return 1;
int ret = 勝ち点得失点差で判定(x, y);
.
.
.
というように、IEqaulity<T>でよく書く参照同値性とnullチェックを入れてみましたところ、40%のスピードアップをマークしました。いやいや、これは他のプログラムのコードも書き直さないかんぞ...まあこのプログラムほど比較系がパフォーマンスに影響しているコードは書いてないから急がなくてもいいですが...
パフォーマンス解析ツールがVisual Studio Ultimateじゃなくても使えるようになったので、モンテカルロシミュレーターをC#6.0で書き直したついでにパフォーマンス測定してみたところ、乱数発生系と順位並べ替えがホットスポットになっていました。まあ、この両者がシミュレーターのキモの部分ですから当たり前なんですが、とりあえず早くできないものかと並べ替え系を検討してみました。
Github
class Main
{
List<TeamResult> Teams;
void Order()
{
Teams = Teams.OrderBy(r => r, new リーグ順位Comparer()).ToList();
}
}
class リーグ順位Comparer : Comparer<TeamResult>
{
static int count1, count2, count3;
public override int Compare(TeamResult x, TeamResult y)
{
count1++;
int ret = 勝ち点得失点差で判定(x, y);
if (ret != 0)
return ret;
count2++;
ret = 当該チームの成績で判定(x, y);
if (ret != 0)
return ret;
count3++;
return 抽選(x, y);
}
}
count1, count2, count3の数を調べたところ、count2とcount3の値が非常に近い値になっていました。抽選になるケースって実際のリーグではほぼ発生しない事象ですので、この二つの変数の値はかなり大きな差が付かねばなりません。当該チームの成績で判定()にバグがあるかと検討しましたがとくにない。はっときがついて、Compareメソッドの先頭に
Github
public override int Compare(TeamResult x, TeamResult y)
{
if (x == y)
{
count0++;
return 0;
}
count1++;
int ret = 勝ち点得失点差で判定(x, y);
if (ret != 0)
return ret;
.
.
.
参照同値性の判定ルーチンを加えたとことろ、count0の値が相当な数になりました。LINQのOrderByメソッドがどんなソートロジックを使っているか知りませんが、ユニークな要素しかない列挙で同じオブジェクトをCompareしているとは予想していませんでした。ICompare<T>の解説ではたいていいきなり内容の比較から入っているサンプルばっかりでしたので、そういうものだと思ってリスト1の書き方をしていたのですが、値型ならいざしらず、参照型の場合は真面目に参照同値性とnullチェックを入れないとだめなのですね。というわけで最終的には
Github
public override int Compare(TeamResult x, TeamResult y)
{
if (ReferenceEquals(x, y))
return 0;
else if (ReferenceEquals(x, null))
return -1;
else if (ReferenceEquals(y, null))
return 1;
int ret = 勝ち点得失点差で判定(x, y);
.
.
.
というように、IEqaulity<T>でよく書く参照同値性とnullチェックを入れてみましたところ、40%のスピードアップをマークしました。いやいや、これは他のプログラムのコードも書き直さないかんぞ...まあこのプログラムほど比較系がパフォーマンスに影響しているコードは書いてないから急がなくてもいいですが...
投稿者 美濃加茂市のIT獣医師 近藤 博 | コメント(0)
コメントする