これは何?
先日。Windows の禁断のAPI だった timeBeginPeriod
の仕様が変わったことに気がついた。
普通に使って良さそうなので使ってみてどうなるか試してみた。
timeBeginPeriod - 禁断のAPI からの仕様変更
先程のリンクにある通り、昔は
Windows 10バージョン 2004 より前のバージョンでは、この関数はグローバル Windows 設定に影響します。
という邪悪な API だった。どこかのプロセスが timeBeginPeriod
を呼ぶと、そのプロセスを動かしている OS 全体の動きが変わり、何も知らない Excel とかも巻き込まれる。
組み込み機器上で動いている Windows 以外で呼ぶのは禁忌とされていた。と思う。
それが
Windows 10 バージョン 2004 以降では、この関数はグローバル タイマー解決に影響しなくなりました。 【略】 この関数を呼び出していないプロセスの場合、Windows は既定のシステム解像度よりも高い解像度を保証しません。
となり、安心して呼んでいい感じになった。
ちなみに「Windows 10 バージョン 2004」の 2004 ってのは '20年4月 のことなので、約三年前。2004年じゃない。
試してみたこと
go の time.Ticker
で 1ms 間隔でなんかする、という処理を試みたらどうなるのかを試してみた。
func test(msg string) {
const size = 5000
ticks := make([]uint64, 0, size)
ticker := time.NewTicker(time.Millisecond)
defer ticker.Stop()
for range ticker.C {
ticks = append(ticks, queryPerformanceCounter())
if size < len(ticks) {
break
}
}
show(msg, ticks)
}
こんな感じ。queryPerformanceCounter
は syscall.LoadDLL
で呼べるようにしてある。
timeBeginPeriod
にわたす値を 1, 15, 16, 256 と変えてみて、処理が来る時間を記録。
ついでに macOS でも記録。
前回時刻との差がどう分布するのかをグラフにしたのが下図。
「before」は、timeBeginPeriod
を呼ぶ前。「tbp:N」は、 timeBeginPeriod(N)
を呼んだあと。
「macOS」は macOS上なので timeBeginPeriod
は無関係。
縦軸はミリ秒。横軸は頻度。
ターゲット環境は、macOS 以外は、いわゆる Windows 11 2022。CPU は Ryzen 3 PRO 4350G。
macOS は、MacBook Pro (Apple M1 Pro 非MAX)。
負荷は特にかけてないけど、負荷がかからないような努力もしていない。全然厳密じゃない感じ。
グラフを見ると
- 1と15 は同じ。
- 呼ぶ前と 16・256 は同じ。
という事になっているように見える。
1 と 15 は、1ms 付近と 2ms 付近の二箇所に山がある。最長でも 3ms ぐらいの間隔になる。
呼ぶ前・16・256 は、16ms 付近に山があり、1ms 間隔になることはない模様。
まとめ
Windows で timeBeginPeriod(1)
の効果は絶大。
今までは time.Ticker
で 1ms と指定しても 16ms にしかならなかったのが、CPU が暇なら、半分以上は 1ms 間隔になるっぽい。
しかし、macOS なんかとくらべると精度が低く、2ms になっちゃうことも多い。残念。
蛇足
1ms より短くできないのも残念。
禁断の関数 timeBeginPeriod
の闇の力を利用していた人は、この仕様変更で困ることになった思う。
そういう意味では、別の API を用意してくれたほうがハッピーだったと思うけど、そうできなかった理由はなんだろうね。