TEST1###
まず、普通にusleep
で1秒スリープしてみます。
usleep(1e6)
で1秒スリープ。
(1e6は1000000、つまり100万マイクロ秒=1秒です)
開始、終了はできるだけ正確に計りたいのでここだけhrtime
(PHP7.3以降で使用可)を使いました。
<?php
sleep(1);
for($i = 0; $i < 1000; $i++) {
$start = hrtime();
usleep(1e6);
$end = hrtime();
printf("%.6f\n", $end[0] - $start[0] + ($end[1] - $start[1]) / 1e9);
}
Windows | Linux |
---|---|
1.000058 1.007640 0.999958 1.000146 1.000745 0.999949 1.000058 1.000078 0.999953 1.000141 |
1.000105 1.000149 1.000147 1.000155 1.000174 1.000152 1.000152 1.000149 1.000150 1.000154 |
1000回ループのうちの最終10回分。 | |
以降のテストも同様。 | |
最後に各テストごとに1000回分の平均をまとめてあります。 |
Windows版PHPではusleep
が指定した値より早く終了してしまう場合があります。
これについては、こちらの方が詳しく調べていらっしゃいます。
PHPのsleep関数とusleep関数の挙動を調べてみた - hnwの日記
同様に、私が調べた限りではtime_sleep_until
やtime_nanosleep
もWindowsでは指定した値よりも早く終了してしまう場合がありました。
TEST2###
次に、microtime
関数を監視して目標時間までループさせる処理にしてみました。
<?php
sleep(1);
for($i = 0; $i < 1000; $i++) {
$start = hrtime();
usleep_(1e6);
$end = hrtime();
printf("%.6f\n", $end[0] - $start[0] + ($end[1] - $start[1]) / 1e9);
}
// 目標時間までループ
function usleep_($microSec) {
$targetTime = microtime(true) + $microSec / 1e6;
while($targetTime > microtime(true));
}
Windows | Linux |
---|---|
1.000002 1.000003 1.000002 1.000002 1.000002 1.000002 1.000002 1.000002 1.000002 1.000002 |
1.000002 1.000003 1.000002 1.000003 1.000002 1.000002 1.000002 1.000003 1.000002 1.000003 |
Windows、Linuxともに単純にusleep
を使った時よりもかなり精度は上がりました。
但しこれは全力でループが回って無駄にCPU負荷が上がってしまう、良くない処理です。
TEST3###
次に、目標までの時間をusleep
でスリープさせてCPU負荷が無駄に上がらないようにしました。
残り時間が少なくなるにつれ誤差が少なくなるよう、usleep
にはループごとの残り時間の半分を指定します。
全体で1秒スリープの場合なら、最初のループで0.5秒スリープ、次のループではその半分の0.25秒スリープ… となり、更に細かくなる頃にはwhile
の条件がfalse
になりループを抜けます。
<?php
sleep(1);
for($i = 0; $i < 1000; $i++) {
$start = hrtime();
usleep_(1e6);
$end = hrtime();
printf("%.6f\n", $end[0] - $start[0] + ($end[1] - $start[1]) / 1e9);
}
// ループごとに目標時間までの半分をusleepで待機
function usleep_($microSec) {
$targetTime = microtime(true) + $microSec / 1e6;
while(($diffSec = $targetTime - microtime(true)) > 0) {
usleep($diffSec * 5e5);
}
}
Windows | Linux |
---|---|
1.015564 1.000082 1.015602 1.001342 1.000101 1.015589 1.000046 1.015584 1.000133 1.000064 |
1.000042 1.000127 1.000039 1.000046 1.000030 1.000032 1.000033 1.000029 1.000044 1.000034 |
CPU負荷はほぼ問題なくなりますが、Windowsでの結果でのばらつきが大きく精度がよくありません。
Windowsではusleep
に指定する値がある程度小さくなると結果が変わらなくなってくるので、その影響が出ているのだと思います。
TEST4###
次にusleep
を使うのはそのままに、while
ループ最後の1/1000秒だけはusleep
無しにしてみます。
<?php
sleep(1);
for($i = 0; $i < 1000; $i++) {
$start = hrtime();
usleep_(1e6);
$end = hrtime();
printf("%.6f\n", $end[0] - $start[0] + ($end[1] - $start[1]) / 1e9);
}
// ループごとに目標時間までの半分をusleepで待機させつつ
// 残り時間の最後1/1000秒だけはusleepなし
function usleep_($microSec) {
$targetTime = microtime(true) + $microSec / 1e6;
while(($diffSec = $targetTime - microtime(true)) > 0) {
if($diffSec > 0.001) usleep($diffSec * 5e5);
}
}
Windows | Linux |
---|---|
1.000117 1.005522 1.000104 1.000095 1.001568 1.000001 1.000106 1.000075 1.000066 1.000001 |
1.000001 1.000002 1.000001 1.000001 1.000001 1.000001 1.000002 1.000001 1.000001 1.000001 |
CPU負荷もほとんど上がらず、Windowsでの精度もそこそこ良い結果になりました。
少なくとも、usleep
を素のまま使うよりは正確かと思います。
CPUパワーに余裕があれば、負担にならない範囲でusleepしない時間を多めにすれば更にばらつきも減るかと思います。
Windows、Linuxそれぞれでtest1.php~test4.phpを1000回ループさせた平均
Windows | Linux | 備 考 | |
---|---|---|---|
test1 | 1.001014 | 1.000146 | 素のusleep() |
test2 | 1.000003 | 1.000003 | microtime()監視でwhileループ (高負荷) |
test3 | 1.002505 | 1.000042 | test2+ usleep()で負荷軽減 |
test4 | 1.000225 | 1.000001 | test3+ 最後1/1000秒のみ usleep()なし |
参考 | 1.001476 | 1.000147 | time_nanosleep(1, 0)での 1000回ループの平均 |
参考でtime_nanosleep
も同じ条件で計測してみましたが、ナノ秒単位で細かく指定はできるものの、精度自体はusleep
とたいして変わらなかったのが意外でした。