Windows版PHPでのusleepは引数の値がどの辺りから結果が横這いになるのか

前回 “[PHP usleepより高い精度でスリープしたい](https://qiita.com/akebi_mh/items/735f563c1f484b14f64c)” にて

> Windowsではusleepに指定する値がある程度小さくなると結果が変わらなくなってくるので

と書きましたが、かなり以前に軽く試した程度でしたので、再度確認してみました。

##スクリプト##

確認に使ったスクリプトは以下になります。

~~~php:test.php
<?php
for($l = 1000000; $l >= 1; $l /= 10) {
    for($sleep_mt = $l; $sleep_mt >= 1; $sleep_mt -= $l / 10) {
        sleep(1);
        $sleep_res[$sleep_mt] = sleep_loop($sleep_mt);
    }
}

print "|argument |      actual |       ideal | accuracy|\n";
foreach($sleep_res as $key => $val) {
    printf("|%8d | %.9f | %.9f | %7.3f%%|\n", $key, $val, $key / 1e6, ($key / 1e4) / $val);
}

function sleep_loop($sleep) {
    $loopCount = ($n = ceil(1e6 / $sleep)) > 1000 ? 1000 : $n;
    printf("usleep(%d) loopcount:%d\n", $sleep, $loopCount);
    $buffArray = [];
    for($i = 0; $i < $loopCount; $i++) {
        $start = gettimeofday();
        usleep($sleep);
        $end = gettimeofday();
        $t = $buffArray[] = $end['sec'] - $start['sec'] + ($end['usec'] - $start['usec']) / 1e6;
        printf("%d: %.9f\r", $i, $t);
    }
    $av = array_sum($buffArray) / $loopCount;
    print "actual(average) ideal        accuracy\n";
    printf("%.9f     %.9f  %7.3f%%\n\n", $av, $sleep / 1e6, ($sleep / 1e4) / $av);
    return array_sum($buffArray) / $loopCount;
}
~~~

##結果##

###Windows###

こちら、Windows版PHPで実行した結果です。
usleepに渡した引数、実測値(数回の平均値)、誤差やオーバーヘッドを皆無とした場合の理想値、理想値に対しての精度になります。

|argument |      actual |       ideal | accuracy|
|:-:|:-:|:-:|:-:|
| 1000000 | 1.004598000 | 1.000000000 |  99.542%|
|  900000 | 0.906128000 | 0.900000000 |  99.324%|
|  800000 | 0.813190000 | 0.800000000 |  98.378%|
|  700000 | 0.704844500 | 0.700000000 |  99.313%|
|  600000 | 0.609229000 | 0.600000000 |  98.485%|
|  500000 | 0.514986000 | 0.500000000 |  97.090%|
|  400000 | 0.407253667 | 0.400000000 |  98.219%|
|  300000 | 0.311733250 | 0.300000000 |  96.236%|
|  200000 | 0.203052400 | 0.200000000 |  98.497%|
|  100000 | 0.108707000 | 0.100000000 |  91.990%|
|   90000 | 0.093679750 | 0.090000000 |  96.072%|
|   80000 | 0.093660769 | 0.080000000 |  85.415%|
|   70000 | 0.078502733 | 0.070000000 |  89.169%|
|   60000 | 0.062928118 | 0.060000000 |  95.347%|
|   50000 | 0.062421800 | 0.050000000 |  80.100%|
|   40000 | 0.046800040 | 0.040000000 |  85.470%|
|   30000 | 0.031325118 | 0.030000000 |  95.770%|
|   20000 | 0.031181740 | 0.020000000 |  64.140%|
|   10000 | 0.015550560 | 0.010000000 |  64.306%|
|    9000 | 0.015549714 | 0.009000000 |  57.879%|
|    8000 | 0.015597080 | 0.008000000 |  51.292%|
|    7000 | 0.015513476 | 0.007000000 |  45.122%|
|    6000 | 0.015546527 | 0.006000000 |  38.594%|
|    5000 | 0.015548400 | 0.005000000 |  32.158%|
|    4000 | 0.015557052 | 0.004000000 |  25.712%|
|    3000 | 0.015410862 | 0.003000000 |  19.467%|
|    2000 | 0.015555192 | 0.002000000 |  12.857%|
|    1000 | 0.015269904 | 0.001000000 |   6.549%|
|     900 | 0.015374730 | 0.000900000 |   5.854%|
|     800 | 0.014939869 | 0.000800000 |   5.355%|
|     700 | 0.015532448 | 0.000700000 |   4.507%|
|     600 | 0.015449516 | 0.000600000 |   3.884%|
|     500 | 0.015041016 | 0.000500000 |   3.324%|
|     400 | 0.015201374 | 0.000400000 |   2.631%|
|     300 | 0.015268615 | 0.000300000 |   1.965%|
|     200 | 0.015033270 | 0.000200000 |   1.330%|
|     100 | 0.015506672 | 0.000100000 |   0.645%|
|      90 | 0.014988844 | 0.000090000 |   0.600%|
|      80 | 0.015085743 | 0.000080000 |   0.530%|
|      70 | 0.014983447 | 0.000070000 |   0.467%|
|      60 | 0.014896100 | 0.000060000 |   0.403%|
|      50 | 0.015412744 | 0.000050000 |   0.324%|
|      40 | 0.015404253 | 0.000040000 |   0.260%|
|      30 | 0.014793728 | 0.000030000 |   0.203%|
|      20 | 0.015019174 | 0.000020000 |   0.133%|
|      10 | 0.015289082 | 0.000010000 |   0.065%|
|       9 | 0.015506825 | 0.000009000 |   0.058%|
|       8 | 0.015441582 | 0.000008000 |   0.052%|
|       7 | 0.015303005 | 0.000007000 |   0.046%|
|       6 | 0.014883630 | 0.000006000 |   0.040%|
|       5 | 0.015491012 | 0.000005000 |   0.032%|
|       4 | 0.015521473 | 0.000004000 |   0.026%|
|       3 | 0.015272744 | 0.000003000 |   0.020%|
|       2 | 0.015507319 | 0.000002000 |   0.013%|
|       1 | 0.015304317 | 0.000001000 |   0.007%|

引数に10000(1/100秒)以下を指定した場合、結果はほとんど同じになりました。
異なるスペックの複数のPCでもだいたい同様の結果でした。

###Linux###

Linuxで実行した場合の結果はこんな感じでした。

|argument |      actual |       ideal | accuracy|
|:-:|:-:|:-:|:-:|
| 1000000 | 1.000119613 | 1.000000000 |  99.988%|
|  900000 | 0.900116641 | 0.900000000 |  99.987%|
|  800000 | 0.800115063 | 0.800000000 |  99.986%|
|  700000 | 0.700112048 | 0.700000000 |  99.984%|
|  600000 | 0.600115420 | 0.600000000 |  99.981%|
|  500000 | 0.500117902 | 0.500000000 |  99.976%|
|  400000 | 0.400114053 | 0.400000000 |  99.971%|
|  300000 | 0.300107420 | 0.300000000 |  99.964%|
|  200000 | 0.200117489 | 0.200000000 |  99.941%|
|  100000 | 0.100117310 | 0.100000000 |  99.883%|
|   90000 | 0.090117912 | 0.090000000 |  99.869%|
|   80000 | 0.080117342 | 0.080000000 |  99.854%|
|   70000 | 0.070116776 | 0.070000000 |  99.833%|
|   60000 | 0.060117303 | 0.060000000 |  99.805%|
|   50000 | 0.050117134 | 0.050000000 |  99.766%|
|   40000 | 0.040117620 | 0.040000000 |  99.707%|
|   30000 | 0.030117647 | 0.030000000 |  99.609%|
|   20000 | 0.020118150 | 0.020000000 |  99.413%|
|   10000 | 0.010116925 | 0.010000000 |  98.844%|
|    9000 | 0.009117167 | 0.009000000 |  98.715%|
|    8000 | 0.008116660 | 0.008000000 |  98.563%|
|    7000 | 0.007117286 | 0.007000000 |  98.352%|
|    6000 | 0.006117258 | 0.006000000 |  98.083%|
|    5000 | 0.005117448 | 0.005000000 |  97.705%|
|    4000 | 0.004108513 | 0.004000000 |  97.359%|
|    3000 | 0.003106908 | 0.003000000 |  96.559%|
|    2000 | 0.002108241 | 0.002000000 |  94.866%|
|    1000 | 0.001107048 | 0.001000000 |  90.330%|
|     900 | 0.001007407 | 0.000900000 |  89.338%|
|     800 | 0.000906241 | 0.000800000 |  88.277%|
|     700 | 0.000807078 | 0.000700000 |  86.733%|
|     600 | 0.000706321 | 0.000600000 |  84.947%|
|     500 | 0.000605894 | 0.000500000 |  82.523%|
|     400 | 0.000484747 | 0.000400000 |  82.517%|
|     300 | 0.000380773 | 0.000300000 |  78.787%|
|     200 | 0.000281204 | 0.000200000 |  71.123%|
|     100 | 0.000161443 | 0.000100000 |  61.941%|
|      90 | 0.000151159 | 0.000090000 |  59.540%|
|      80 | 0.000141270 | 0.000080000 |  56.629%|
|      70 | 0.000142765 | 0.000070000 |  49.032%|
|      60 | 0.000119653 | 0.000060000 |  50.145%|
|      50 | 0.000109219 | 0.000050000 |  45.779%|
|      40 | 0.000099035 | 0.000040000 |  40.390%|
|      30 | 0.000088091 | 0.000030000 |  34.056%|
|      20 | 0.000078189 | 0.000020000 |  25.579%|
|      10 | 0.000067107 | 0.000010000 |  14.902%|
|       9 | 0.000066188 | 0.000009000 |  13.598%|
|       8 | 0.000065196 | 0.000008000 |  12.271%|
|       7 | 0.000064239 | 0.000007000 |  10.897%|
|       6 | 0.000063161 | 0.000006000 |   9.500%|
|       5 | 0.000061892 | 0.000005000 |   8.079%|
|       4 | 0.000060883 | 0.000004000 |   6.570%|
|       3 | 0.000059732 | 0.000003000 |   5.022%|
|       2 | 0.000058932 | 0.000002000 |   3.394%|
|       1 | 0.000057835 | 0.000001000 |   1.729%|

値が小さくなるに従ってオーバーヘッドの影響も無視できなくなってくるので精度が落ちるのは仕方ありませんが、Windows版と比べたらかなり低い値まで信用できるかな、と思います。

##追記##

Windows版PHPでも、同時に動いているソフトウエア次第でusleepの値が横這いになり始める閾値が変わるようでした。

|argument |      actual |       ideal | accuracy|
|:-:|:-:|:-:|:-:|
|  100000 | 0.100642500 | 0.100000000 |  99.362%|
|   90000 | 0.090570583 | 0.090000000 |  99.370%|
|   80000 | 0.080499308 | 0.080000000 |  99.380%|
|   70000 | 0.070486467 | 0.070000000 |  99.310%|
|   60000 | 0.060393294 | 0.060000000 |  99.349%|
|   50000 | 0.050311800 | 0.050000000 |  99.380%|
|   40000 | 0.040265600 | 0.040000000 |  99.340%|
|   30000 | 0.030161941 | 0.030000000 |  99.463%|
|   20000 | 0.020167180 | 0.020000000 |  99.171%|
|   10000 | 0.010274510 | 0.010000000 |  97.328%|
|    9000 | 0.009216366 | 0.009000000 |  97.652%|
|    8000 | 0.008228112 | 0.008000000 |  97.228%|
|    7000 | 0.007263986 | 0.007000000 |  96.366%|
|    6000 | 0.006263365 | 0.006000000 |  95.795%|
|    5000 | 0.005298985 | 0.005000000 |  94.358%|
|    4000 | 0.004333180 | 0.004000000 |  92.311%|
|    3000 | 0.003362692 | 0.003000000 |  89.214%|
|    2000 | 0.002429720 | 0.002000000 |  82.314%|
|    1000 | 0.001439262 | 0.001000000 |  69.480%|
|     900 | 0.000987596 | 0.000900000 |  91.130%|
|     800 | 0.000992589 | 0.000800000 |  80.597%|
|     700 | 0.000992307 | 0.000700000 |  70.543%|
|     600 | 0.000988720 | 0.000600000 |  60.685%|
|     500 | 0.000987593 | 0.000500000 |  50.628%|
|     400 | 0.000987899 | 0.000400000 |  40.490%|
|     300 | 0.000988207 | 0.000300000 |  30.358%|
|     200 | 0.000988125 | 0.000200000 |  20.240%|
|     100 | 0.000988660 | 0.000100000 |  10.115%|
|      90 | 0.000988996 | 0.000090000 |   9.100%|
|      80 | 0.000988034 | 0.000080000 |   8.097%|
|      70 | 0.000993000 | 0.000070000 |   7.049%|
|      60 | 0.000988955 | 0.000060000 |   6.067%|
|      50 | 0.000988196 | 0.000050000 |   5.060%|
|      40 | 0.000988778 | 0.000040000 |   4.045%|
|      30 | 0.000988507 | 0.000030000 |   3.035%|
|      20 | 0.000988754 | 0.000020000 |   2.023%|
|      10 | 0.000988374 | 0.000010000 |   1.012%|
|       9 | 0.000988151 | 0.000009000 |   0.911%|
|       8 | 0.000988197 | 0.000008000 |   0.810%|
|       7 | 0.001001757 | 0.000007000 |   0.699%|
|       6 | 0.000987563 | 0.000006000 |   0.608%|
|       5 | 0.000987617 | 0.000005000 |   0.506%|
|       4 | 0.000988710 | 0.000004000 |   0.405%|
|       3 | 0.000988145 | 0.000003000 |   0.304%|
|       2 | 0.000988444 | 0.000002000 |   0.202%|
|       1 | 0.000988313 | 0.000001000 |   0.101%|

100000より上は記載を省略していますが、Windowsでも10000を下回っても結果が下がり続け、1000未満で横這いになり始めるといったケースがありました。

###何が影響するのか確認###

~~~php:test.php
<?php
while(true) {
    $r = [];
    $t = time();
    while($t === time()) {
        $start = gettimeofday();
        usleep(1000);
        $end = gettimeofday();
        $r[] = $end['sec'] - $start['sec'] + ($end['usec'] - $start['usec']) / 1e6;
    }
    printf("%.9f\n", array_sum($r) / count($r));
}
~~~

上記のスクリプトを実行させながらいくつかの別のソフトウェアを立ち上げたり終了させたりしてみたところ、Google Chrome(確認したバージョンは81.0.4044.138)を実行した時に大きく変動するようでした。
とくに、タイマー系のjavascriptの処理の影響が大きいようで、Chromeで例えば

~~~js
setInterval(1000, function(){});
~~~

を走らせているだけでもusleepの精度が上がり、Chromeを閉じると元に戻りました。
他のブラウザだと、firefoxは影響なし、EdgeやIEはChromeの半分くらい影響、といった感じでした。