現在VB6.0からのシステムリプレース案件で.NET Framework (C#)を用いてアプリケーションを開発をしているが、顧客の業務端末スペックが厳しいため本番導入後に動作のもたつきが発生しそう、という懸念材料がある。
具体的には
- 現行環境
- OS: WindowsXP
- クライアント: VB6.0
- データベース: SQL Server 2000
- 二台の端末で運用(親機にはDBとクライアント、子機にはクライアントのみ)
という化石のような環境。まだ生きていたのか(案外残ってる)。
ひとまずWindows7に対応させたい
この現行WinXP端末が既に壊れそうということもあり、ひとまず現場にある(得体の知れない)Windows7端末に移し替えて本番稼働→現在調達中のWindos10端末が届けばそちらに再度移行、という話になった。
ただ、その(得体の知れない)Windows7端末は見るからに古く、まずSSDもメモリも碌に積んでいないだろうことは想像に容易い。
当方の開発環境もさほど良いものではないが、SSDに換装してディスクI/Oの遅延を減らすことで快適に動いている。が、はたして現場の端末でまともに動くのかどうか...。
(得体の知れない)Windows7を再現する
とりあえずVM環境にWindows7 64bitをインストールし、HDD上で動作させてみる。
...引くぐらい遅い。
アプリケーション自体が起動するまで10秒、そこから画面を一つ開き終わるまで更に10秒ぐらい待たされる。一度画面を表示してしまえばその後は他の画面を開いても割とサクサク動く。初回起動がネックになるようだ。
ちなみ環境ごとSSDに移してみるとまあまあ許容範囲内に収まったのでやはりディスクアクセスの遅さが致命的なのだが、顧客の業務端末を開腹してSSDに乗せ換えるわけにはいかないので、他の方法を探してみる。
.Net Frameworkのしくみ
調べてみたところ、どうやらVB.Netではコンパイルして出力したEXEファイルは最終のネイティブコードではなく、MSIL(Microsoft Intermediate Language)と呼ばれる中間コードとして出力されるそう。
そしてEXE実行時に.NET Frameworkが持つJIT(Just-In-Time)コンパイラによってネイティブコードにコンパイルされ、アプリケーションが実行されるらしい。
つまりEXEを起動するたびにコンパイルが走っているため、どうしてもワンテンポ遅くなる、ということらしい。
「ngen.exe」で先にコンパイルしてしまおう
ネット上で広く公開するようなアプリケーションの場合は便利なのかもしれないが、今回のように使用する端末が限られている(決まっている)場合、わざわざ中間コードを介するメリットは少ない。
ということで、先にこのコンパイルまでしてしうことができるのがngen.exeである。
詳しくは↑こちらに書かれているが、実際にプログラムを実行する端末上で
C:\Windows\Microsoft.NET\Framework\v4.0.30319\ngen.exe install C:\Hoge.exe
と叩いてやることで、先にネイティブイメージを生成してくれるそうだ。
これでEXE実行時に直接ネイティブコードからアプリケーションが起動するため、動作が早くなるということらしい。
ちなみに「プログラムを移動する、あるいは上書きすると効果が無くなってしまうため注意」とのこと。
ということで実際に試してみたが目に見える効果は無かった。
どうやら使用するngen.exeが間違っていたようだ。64bit環境では
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ngen.exe install C:\Hoge.exe
こっちが正しいらしい。参考元↓
そしてもう一つ。
「ビルド時のターゲットCPUを「AnyCPU」または「x64」に設定した場合」に初回起動が遅くなる可能性があるらしい。
詳細は↑を読んで欲しいが、64Bit環境でx64向けにビルドしたEXEを動かすとべき乗倍にモリモリとメモリを喰ってしまい、パフォーマンスが落ちるということらしい。
メモリを潤沢に積んでいる端末なら大丈夫なのだろうが、貧弱な環境ではかなりパフォーマンスに影響があるようだ。
もう一つの方法:x86でビルドする
ということで、↑のリンクに書かれているもう一つの方法は「ビルド時のターゲットCPUを「x86」に設定する」だ。
つまり「x64向けにビルドしたらメモリモリモリしちゃうんだから、x86向けにビルドしてWOW64上で動作させてしまえばいいじゃない。」ということ。
いやまぁそれはそうなんだが、64BitのOSを載んでいる意味・・・。
なんというか力業感は否めないが、大量のメモリを使わないアプリケーションであればこの方法もありだろう。
ということでテストしてみる
パターンとしては
- Win7 64Bitでx64ビルドしたEXEを実行
- Win7 64Bitでx86ビルドしたEXEを実行
- Win7 64Bitでx64ビルドしたEXEをngenしてから実行
- Win7 64Bitでx86ビルドしたEXEをngenしてから実行
- Win7 32Bitでx86ビルドしたEXEを実行
- Win7 32Bitでx86ビルドしたEXEをngenしてから実行
以下の5パターンで「EXE起動→画面1を表示→画面1を閉じる→画面2を表示」と画面遷移した時の所要時間を計ってみた。
が、結果として実際の数字を公表するほど有意性が出なかった。どのパターンでも2回目以降の起動は即座にに処理が終わるのだが、1回目の起動は遅いときは20~30秒ぐらいかかるし、早い時は10秒かからない。原因不明。
EXEファイルの場所を換えたりビルドし直してみたりしたが変わらず。どうも同じEXEを繰り返し実行すると(Windowsの再起動を挟んでも!)即座に起動するようなのでWindows側にキャッシュされているのか?とも思ったがWindowsの再起動を挟んだらキャッシュも消えるよね?消えないのか?謎だ。
初回が遅い問題のもう一つの解決策
こちらのフォーラムでは「Windows起動時にスタートアップで当該EXEをダミー起動させる。」という方法が提案されている。
「初回起動が遅いなら、Windows起動時に初回起動させておけばいいじゃない」というわけだ。
もう少し詳しく書くなら、アプリケーションに初回起動用の引数を用意しておき、「引数あり」で起動した際は即座に終了するロジックを作っておく。そしてスタートアップには「引数あり」で登録しておけば、Windows起動時にそのアプリケーションが起動→即終了する。これで初回起動達成、キャッシュが残るため次からの起動は早くなる、というわけだ。
これも力業ではあるが、実際の所どう頑張っても2回目以降の起動しか早くならないのであれば他に手はない。
当然Windowsの立ち上がり速度は遅くなるが、アプリケーションの体感速度が致命的に遅くなってしまう問題が残り続けるよりはいいだろう。
ということで
力業が目立つものの、いくつかの解決法を見つけることができた。
どの方法を選ぶかは実際に顧客の業務端末で動かしてみてからの判断になるが、今のところ全て採用することになりそうな気がする。
インストールと称してバッチファイルを配布すればngen.exeの実行もスタートアップへの登録もできるだろう。
結局はっきりとした原因が分からないのが非常にモヤモヤするが、顧客がそのうちWindows10に移行するつもりなので端末も新しくれば改善されるだろう。
...ちゃんとした端末入れてくれるよね?頼むからSSDは入れてほしいなぁ...。
コメントする