Top > Programmingとか > VB / VB.NET > Timerコントロールと時間計測

1998年03月22日

02 Timerイベントの発生頻度はMax18回/秒ではない

日付を管理していなかったので、正確な記述日時がわかりません。
ので、サイト「猿頁」開設日としました。御了承ください。
検証環境 自作ATX(Cerelon500→600MHz/VT6X4/256MB)
Windows2000ProSP2/IE6.00Preview/VS6SP5/MSDN-Lib Jan,2001

さて、ここからが今回の本題。

今回なにげにもぅ一度実行してみたところ、10秒間で998~999回(約100回/秒)といぅ結果になりました。

…なぜだ?

当時と現在の動作環境の差を考えてみると、[Pentium166MHz/Win95]→[Celeron600MHz/Win2000Pro]って感じですので、まずはこのへんが原因かと。ので職場やらなんやらのさまざまな環境で同じプログラムを実行してみました。

結果。CPUの種類やクロックには依存せず、Win95/98/Meで約18回/秒、WinNT4/2000で約100回/秒となりました。どぅやら95系・NT系といぅ区別で異なるよぅです。

では、この発生のタイミングはいかなるトリガによるものなのか。

実際にTimerコントロールを貼り付けただけのプログラムを実行させてspy++でプロセスごとフックさせてみたところ、Intervalプロパティに設定したミリ秒ごとに「WM_TIMER」メッセージが流れていました。
このWM_TIMERメッセージは、SetTimer APIなどで定期的に供給されるようにセットされる自動発生メッセージで、その発生トリガはシステムタイマ割り込み(IRQ 0)なんですね。

システムタイマ割り込みは、マシン起動時の初期値では55ms(約1/18秒)であり、これはWin9x系のタイマ割り込みの値そのものです。従って、

Win9x系ではマシン起動時の初期値のまま、
WinNT/2000系では10msにインターバルを変更して動作する

といぅ結論になりましょうか。



ちなみに。

ということは、もし工場出荷時の起動時初期値が55msではないマシンがあったとしたら、Win9x系のOSではその影響をモロにかぶるといぅことになるかもしれませんね。
ROM-BASICやMS-DOSがメジャーだった頃のアクションゲームなどはわざわざこの割り込み間隔を変更して操作性を向上させているものもありました(しかも終了時に復元しないプログラムもありました…ので、終了後ほかのプログラムがまともに動作せず、そのつどリセットするといぅお粗末な対処法を強いられたものです)。

WinNT/2000系がどのタイミングとどのモジュールで割り込み間隔を変更しているのかまでは現時点では突き止められなかったのですが、もしかするとそのへんのモジュールのチューニングや差し替えで、割り込み間隔を任意に変更することができるかもしれません。また、完全に起動してから、任意のプログラムから変更することも可能かもしれません…もっともこれは可能性薄。変更前に起動しておいたプログラムの動作がおかしくなりかねないので、プロセス単位で割り込み間隔を指定できない限りはOSレベルでロックかけるのがまっとぅでしょうから。



さらにちなみに。

前述引用したMSDNの文章では、「タイマーは、(中略)必要なときにシステムクロックを調べます」「システムは、1秒に18のクロック信号を発生させます」とあります。
実はVisualStudioのヘルプ/MSDNは、基本的に16/32bit系OS用開発環境の進化変遷に合わせて増補改訂を積み重ねてあれだけ肥大した量になっています。MSDNをあちこちひっくり返して読んでみると、「その当時はそぅだったかもしれないけど、今は違うよ」って記述がそこかしこに存在します。
で、前述の文章は、VBが初めて32ビットOS対応(当時はWin95のみ)となったVer.4のヘルプで初めて書き起こされた文章です。で、それ以来VBがVer.6になろうがWinが2000(もぅじきXP?)になろぅが改定されていないよぅです。
ので、あの文章は見えないところに「Windows95上でのVB4では」といぅ枕詞があるものとして読むと正確な記述になります。また、文章の最後には「ただし今後の新マシン・新OS・VB/VSの新バージョンにおいては動作が変更になる場合があります」って一文もあると考えておくと精神衛生上いいかもしれません。

まぁMSDNは増補改訂量がものすごく、すこし混沌としてきているよぅな気もします。VB6から追加された新機能などは一応記述がありますが、以前の記述とうまくリンクが取れていません(やっぱInStrとInStrRevが関連付いていないのは使いにくいよ、とか)し、ツリーと表示されているページの同期も取れませんし(私は検索で引っかけた項目の前後を読んで全体像を把握することが多いので、この機能が正常に動作しないのはイタいんですよねぇ)。

どっかでえいやっと全体の構成と文章の鮮度を仕切り直してほしくもあるんですが、英語原文→和訳の作業もまだ完了していないなどの状況を考え合わせると、ちょっと望み薄な希望なんでしょうねぇ…。

01 Timerイベントの発生頻度はMax18回/秒

日付を管理していなかったので、正確な記述日時がわかりません。
ので、サイト「猿頁」開設日としました。御了承ください。
検証環境 自作ATX(Cerelon500→600MHz/VT6X4/256MB)
Windows2000ProSP2/IE6.00Preview/VS6SP5/MSDN-Lib Jan,2001

本コンテンツは、旧「TimerコントロールのIntervalプロパティに「1」をセットしたのに、1ミリ秒ごとにTimerイベントが発生しない。」の加筆訂正版です。
当時とはマシン環境も変わってきましたし、単に私が間違っているだけの部分もありましたし。



まず、前振りとして前回の論旨。

Timerコントロールの発生間隔を設定するIntervalプロパティは、ミリ秒単位で設定できるよぅになっていますが、発生間隔のコントロールはミリ秒単位ではありません。

MSDNの[Visual Studio 6.0 ドキュメント]-[Visual Basicドキュメント]-[リファレンス]-[ランゲージリファレンス]-[プロパティ]-[I]-[Intervalプロパティ]には

タイマー(Timer)コントロールのTimerイベントが発生する時間間隔をミリ秒単位で設定します。値の取得も可能です。
  • タイマーイベントプロシージャは、Intervalプロパティの設定時間が経過するたびに、繰り返し実行されます。
  • および同[Visual Studio 6.0 ドキュメント]-[Visual Basicドキュメント]-[プログラミングガイド]-[Visual Basicを使ってできること]-[Visual Basicの標準コントロールの使用]-[タイマーコントロールの使用]では、

    Interval プロパティには、タイマー (Timer) コントロールを使ってプログラミングを行う際に考慮すべき、いくつかの制限があります。
    • システムに負荷のかかるプログラム ループ、大量の計算処理、またはドライブ、ネットワーク、ポートへのアクセスが発生すると、Intervalプロパティに指定した時間間隔でTimerイベントが発生しない可能性があります。
    • 時間間隔には、0から64,767までの値を指定することができます。つまり、最大値(約64.8秒)を指定しても、1分を大幅に超えることはありません。
    • 指定した時間間隔は、正確に時間どおりに経過するとは限りません。正確性を確保するため、タイマーは、内部で蓄積時間を追っていくのではなく、必要なときにシステムクロックを調べます。
    • システムは、1秒に18のクロック信号を発生させます。したがって、Intervalプロパティはミリ秒単位で指定されますが、実際の時間間隔の精度は、せいぜい18分の1秒です。
    とあります。

    以上よりTimerイベントは1秒あたり18回、1000/18≒55.56秒おきに発生するって理解になります。

    で、実際にそのへんを測るプログラムを組んで確認。
    10秒ループさせて169回、170回、172回、169回、169回のプロシージャ実行回数、平均16.9回/秒となりました。

    62.5ミリ秒(10秒回した場合、計算上では160回)では、最大よりも多い回数が出た、ってことになっちゃうので理屈に合いません。それよりも計測のロジックに取られる部分やマシンに常駐している他プロセスが足を引っ張って最高速が出なかったと考えるほうが自然ですね。

    したがってまぁ当時の結論としては、

    • 最大18回/秒というMSDNの記述を信じてもいいのではないか。

    • 18回に届かなかった誤差分は計測ロジック部や常駐他プロセス分だろう。
    としました。

    実測に使ったプログラムはこちら