MENU

【C#】ペントミノ2Dパズルを解く(9)終わりに

この連載をお読みいただきありがとうございました。

このプログラムはVB版をC#マイグレーションしたものですが、実は筆者にはC#の経験はほとんどありませんでした。VBの方はかなり詳しいつもりですが、C#など中括弧のある言語は苦手でした。今回のマイグレーションVBC#の違いは思ったよりも小さいことが分かりました。あとは慣れるだけです。

ともあれVB版とC#版の2つのプログラムが完成したのですから、実行速度を比較してみましょう。なおPCのスペックは次のとおりです。

項目 内容
OS Windows 11 Home 64bit
CPU インテル Core i7-8700 3.2GHz
RAM 16GB
外部記憶 SSD 1TB

・なぜかC#が遅い


ちょっと見づらいですが、VB(左)の1分20秒に対しC#(右)は9分8秒もかかっています。その差はなんと約7倍です。筆者はなんとなくですがC#の方が速いと思っていましたがまるで逆の結果です。この差の原因は何でしょう。

・原因はリストビューの描画速度

まず原因として最初に考えたのは Application.DoEvents() です。「キャンセルボタン」を受け付けるためにイベントの発生を頻繁に許可しています。この割り込みが処理速度にかなり影響しているのではないかと考えました。次に考えられるのはプログレスバーの描画です。これも頻繁に計算しています。最後はリストビューの完成表示です。これはプログラムの核になるのでやめることはできませんが、原因を探るために表示自体をやめて検証してみました。以下は結果です。

No 検証項目 VB C#
変更なし 1:20 9:08
1 Application.DoEvents() をやめる 1:20 9:08
2 プログレスバーの描画をやめる 1:12 8:53
3 リストビューの完成表示をやめる 0:18 0:12

驚きの結果です。リストビューの完成表示がプログラムの大半を占めていました。特にC#の場合は表示をやめるとVBよりも速くなりました。どうも原因はリストビューの描画にありそうです。

・リストビューの改善

原因がリストビューにあることがはっきりしたので Application.DoEvents() とプログレスバーについては手を加えないことにしました(元のまま)。
リストビューについては次の項目を検証してみました。

No 検証項目 VB C#
変更なし 1:20 9:08
4 DoubleBuffered プロパティを True にする 1:17 8:28
5 BeginUpdate, EndUpdate を記述する 1:20 1:10
6 Refresh メソッドの記述を削除 1:17 1:06
4.DoubleBuffered プロパティを True にする

DoubleBuffered は ListView のプロパティですがスコープは private です。外からは変更できないのでListViewを継承したカスタムコントロールを作りその中で値を操作します(カスタムコントロールについては説明を省きます)。Trueにすると描画時の画面のちらつきが収まります。ちらつきがない代わりに描画速度が犠牲になるのではないかと思っていましたが、結果は逆で若干の速度の改善が見られました。

5.BeginUpdate, EndUpdate を記述する

BiginUpdateとEndUpdateをリストビューの描画前後に挟んで記述することで、速度の改善がみられるはずです。結果はVBでは若干の改善、C#では劇的な改善が見られました。どうもこのあたりがVBC#で言語の作り方が違うような気がします。C#での影響の大きさに対してVBはまるで無視に近い影響しかないのが解せません。

6.Refresh メソッドの記述を削除

Refreshは完成表示の中に記述しています。他の箇所で Application.DoEvents() メソッドを頻繁に発行してフォームが再描画されるのであれば、本来は不要な記述です。さらに言えば前項の BeginUpdateとEndUpdateの記述も 不要となるはずです。C#のRefreshメソッドがどうしてこれだけコストがかかるのかは不明ですが、VBの7倍も時間がかかるのは Refresh メソッドの削除で解消したのは事実です。

・次のプログラムは3次元への展開

今回のプログラムは2次元のペントミノでしたが、正方形を立方体にした3次元のペントミノの箱詰めパズルに挑戦したいと思っています。今回のプログラムを少し変更すれば比較的簡単にできるのではないかと思っています。

ご期待ください。