院長のメモ帖
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%のスピードアップをマークしました。いやいや、これは他のプログラムのコードも書き直さないかんぞ...まあこのプログラムほど比較系がパフォーマンスに影響しているコードは書いてないから急がなくてもいいですが...
2015年10月10日 土曜日
J2の行方
10月に入りJ2リーグも佳境に入りました。わがFC岐阜は、一時期の低迷をどうにか脱しつつあるようですが、まだ降格圏の少し上、全然安心できません。
きょうのJ2結果は引分けが多く、なんと17位から22位まで全チームが引分け!おかげで降格争いに変化が起こりませんでした。
しかし残り6節、まだまだ降格可能性のあるチーム多いですが、モンテカルロシミュレーションで、今年のJ2リーグの結果を予想してみました。
まずは、優勝・昇格争いから。単位は%です。10%以上の項目は太字にしています。
チーム名 | 昇格(1-2) | プレーオフ(3-6) | 合計(1-6) |
大宮 | 99.92 | 0.08 | 100.00 |
磐田 | 60.98 | 39.02 | 100.00 |
福岡 | 24.62 | 75.34 | 99.78 |
C大阪 | 14.34 | 85.44 | 99.78 |
東京V | 0.09 | 56.44 | 56.53 |
長崎 | 0.03 | 45.90 | 45.93 |
千葉 | 0.02 | 45.45 | 45.47 |
愛媛 | 0.01 | 38.14 | 38.15 |
札幌 | 0.00 | 5.09 | 5.09 |
岡山 | 0.00 | 3.17 | 3.17 |
熊本 | 0.00 | 1.91 | 1.91 |
金沢 | 0.00 | 1.64 | 1.64 |
徳島 | 0.00 | 1.07 | 1.07 |
北九州 | 0.00 | 1.15 | 1.15 |
群馬 | 0.00 | 0.13 | 0.13 |
横浜FC | 0.00 | 0.03 | 0.03 |
というわけで、大宮が圧倒的に有利。優勝確率も98.02%、もう当選確実ですね。2位争いは少し熾烈で磐田、福岡、セレッソのどこが来るか、まだなんとも言えませんね。プレーオフは実質的に現在8位の愛媛までに絞られたでしょうか。
それでは、肝心の降格争いです。
現順位 | チーム名 | 入替戦(21) | 降格(22) | 合計(21-22) |
---|---|---|---|---|
15 | 群馬 | 0.25 | 0.02 | 0.27 |
16 | 横浜FC | 0.92 | 0.12 | 1.04 |
17 | 讃岐 | 4.73 | 1.05 | 5.78 |
18 | 京都 | 8.30 | 2.98 | 11.28 |
19 | 岐阜 | 9.44 | 3.17 | 12.61 |
20 | 水戸 | 18.14 | 7.90 | 26.04 |
21 | 大分 | 33.06 | 30.10 | 63.16 |
22 | 栃木 | 25.13 | 54.66 | 79.79 |
一応、12-14位も0.01-0.02%の確率で入れ替え戦ですが、省略しました。表中でも合計5%以上の17位讃岐以下に降格争いは絞られてるとみてよいでしょう。
わが岐阜は降格は3.17%なので、やっと尻に火が付いた状態からは脱出できましたが、入れ替え戦もあわせた合計確率は12.61%とまだまだ実現可能な数字です。本当は今日京都に勝って順位が入れ替われば最高でしたが、みんな仲良く引分けは下位が不利ということで、まあ、よかった方だったのかなと感じるべきでしょうか...
ちなみに、今年から、JリーグのWebPage直接読み込んでシミュレーションできるようになりました。WebClientで読み込んで、NuGetのSgmlReaderでXMLに変換し、LinqToXMLで解析しました。マークアップ言語はほとんど素人ですが、LINQにさえもちこめればこっちのもの。一晩で作成できました。いままで手で入力してたのに比べると、ほんとに楽です。
2015年10月 1日 木曜日
FileInfoのIsReadOnlyはRefresh()とともに
ちょっとしたファイルバックアップコードを書いていてハマりました。
private static void CopyFile(FileInfo f, FileInfo bak)
{
if (!bak.Exists)
f.CopyTo(bak.FullName);
bak.IsReadOnly = true;
}
bakファイルが存在しなければfファイルをコピーしてbakを作成し、読み出し専用フラグを設定する、というだけの簡単なコードですが、出来上がったファイルは、readOnlyどころか、Hidden, Systemその他確認できる限りのフラグが立ってしまっていました。確認のため次のコードを実行した所、
private static void check(FileInfo bak)
{
bak.IsReadOnly = true;
}
問題なく、ReadOnly属性のみついていました。
少しデバッグして気が付いたのですが、まず、bak.Existを呼び出した時点で、bakにはファイルが存在していないことが
キャッシュされます。これは、次行のファイルコピーされた後も変化しません。その次にbakに属性をセットるわけですが、この時点でbakのAttribiutesプロパティーは-1になっており、IsReadOnlyは以下のコードのようにファイルAttribiutesのReadOnlyビットを操作しているのでキャッシュされた-1に対する演算になってしまします。そしてこの結果が実ファイルに書き込まれるので、あらゆるファイル属性がonになって隠しシステムファイルが誕生してしまったわけです。
public bool IsReadOnly {
get {
return (Attributes & FileAttributes.ReadOnly) != 0;
}
set {
if (value)
Attributes |= FileAttributes.ReadOnly;
else
Attributes &= ~FileAttributes.ReadOnly;
}
}
ですから、最初のコードは
private static void CopyFile(FileInfo f, FileInfo bak)
{
if (!bak.Exists)
{
f.CopyTo(bak.FullName);
bak.Refresh();
}
bak.IsReadOnly = true;
}
と、RefreshしてからIsReadOnlyする必要がありました。
FileInfoやDirectoryInfoでIO操作するときはキャッシュの存在を頭に入れておかないと、時としてとんでもないことが起こりますね(-_-;)
private static void CopyFile(FileInfo f, FileInfo bak)
{
if (!bak.Exists)
f.CopyTo(bak.FullName);
bak.IsReadOnly = true;
}
bakファイルが存在しなければfファイルをコピーしてbakを作成し、読み出し専用フラグを設定する、というだけの簡単なコードですが、出来上がったファイルは、readOnlyどころか、Hidden, Systemその他確認できる限りのフラグが立ってしまっていました。確認のため次のコードを実行した所、
private static void check(FileInfo bak)
{
bak.IsReadOnly = true;
}
問題なく、ReadOnly属性のみついていました。
少しデバッグして気が付いたのですが、まず、bak.Existを呼び出した時点で、bakにはファイルが存在していないことが
キャッシュされます。これは、次行のファイルコピーされた後も変化しません。その次にbakに属性をセットるわけですが、この時点でbakのAttribiutesプロパティーは-1になっており、IsReadOnlyは以下のコードのようにファイルAttribiutesのReadOnlyビットを操作しているのでキャッシュされた-1に対する演算になってしまします。そしてこの結果が実ファイルに書き込まれるので、あらゆるファイル属性がonになって隠しシステムファイルが誕生してしまったわけです。
public bool IsReadOnly {
get {
return (Attributes & FileAttributes.ReadOnly) != 0;
}
set {
if (value)
Attributes |= FileAttributes.ReadOnly;
else
Attributes &= ~FileAttributes.ReadOnly;
}
}
ですから、最初のコードは
private static void CopyFile(FileInfo f, FileInfo bak)
{
if (!bak.Exists)
{
f.CopyTo(bak.FullName);
bak.Refresh();
}
bak.IsReadOnly = true;
}
と、RefreshしてからIsReadOnlyする必要がありました。
FileInfoやDirectoryInfoでIO操作するときはキャッシュの存在を頭に入れておかないと、時としてとんでもないことが起こりますね(-_-;)