你能弄清楚这个PHP计时问题吗?

时间:2011-01-21 23:58:05

标签: php time microtime

有人告诉我为什么当我运行带有以下内容的脚本然后在 5 秒之后停止它,我需要将经过时间除以2以获得正确的脚本执行时间? / p>

ignore_user_abort(true); set_time_limit(0); 

$begin_time = microtime(true);

$elapsed_time = 0;

while(!connection_aborted()) {
    echo ' ';
    flush();
    usleep(1000000);
}

$elapsed_time = microtime(true) - $begin_time;

$timer_seconds = $elapsed_time; //10 seconds

$timer_seconds = $elapsed_time / 2; //5 seconds


/*I am writing to a DB - but you can use this to test */
$fp = fopen('times.txt', 'w');
fwrite($fp, 'Time Elapsed: '.$timer_seconds);
fclose($fp);

随意尝试代码,因为它让我感到困惑,为什么$elapsed_time需要除以2。也许我误会了什么?

感谢大家的帮助

更新

我已经更新了代码,以便任何人都可以尝试这样做,它会写入文本文件以查看输出。

6 个答案:

答案 0 :(得分:13)

实验

原始代码发生重大变化:

1)使用implicit_flush并在执行任何操作之前刷新所有缓冲区 2)代码不输出空格,而是输出迭代次数和1023字节的其他数据,告诉浏览器我们要显示的输出量很大。一个正常的已知技巧。
3)除了在输出文本文件中保存时间外,它还保存了代码运行的总迭代次数。

使用的代码:

<?php
// Tricks to allow instant output
@ini_set('implicit_flush', 1);
for ($i = 0; $i < ob_get_level(); $i++)
    ob_end_flush();
ob_implicit_flush(1);

//Your Code starts here
ignore_user_abort(true);
set_time_limit(0); 

$begin_time = microtime(true);
$elapsed_time = 0;

while(!connection_aborted())
{
    //this I changed, so that a looooong string is outputted
    echo $i++.str_repeat(' ',1020).'<br/>';
    flush();
    usleep(1000000);
}

$elapsed_time = microtime(true) - $begin_time;
$timer_seconds = $elapsed_time; //10 seconds

//Writes to file the number of ITERATIONS too along with time
$fp = fopen('4765107.txt', 'w');
fwrite($fp, 'Time Elapsed: '.$timer_seconds);
fwrite($fp, "\nIterations: ".$i);
fclose($fp);
?>

现场演示:


我得到了什么:

1)当代码运行10次迭代并且单击浏览器上的STOP按钮时,输出文件显示13次迭代,并且花费约13.01秒。

2)当代码运行20次迭代并且单击浏览器上的STOP按钮时,输出文件显示23次迭代,并且采用~23.01秒。


推论&amp;结论

1)单击STOP按钮但单击2-4秒后脚本实际上不会停止。因此,浏览器中会出现更多迭代。

2)迭代次数为SAME,即执行所需的秒数,如输出文件所示。

因此,没有错误,显然没有错误,只是单击STOP按钮和实际停止的脚本之间的延迟时间。


备注:

1)服务器:Linux VPS 2)测试的客户:Firefox和Chrome 3)由于脚本在单击STOP后2-4秒结束,因此当前测试的输出文件大约需要3-4秒。

答案 1 :(得分:9)

摘要:(这篇文章在我测试各种途径时变成了史诗)

PHP通常需要两次循环迭代来检测断开连接或提供输出。此延迟可能来自Web服务器软件,主机,客户端计算机和客户端浏览器,但它应根据每次迭代的睡眠而变化。更可能的延迟来自PHP的内部执行或输出过程(可能来自一个小的内部缓冲区或中断处理过程)。

史诗贴:

从[刷新]或网址提交计算执行时间并不是一个准确的起点 - 可能首先需要执行任意数量的步骤,这可能会增加延迟:

  1. 需要DNS查找(使用TCP开销)
  2. 与服务器建立的TCP连接
  3. Web Server创建线程或子项
  4. Web Server决定如何处理请求
  5. PHP可能需要启动
  6. PHP可能需要将您的来源转换为opcode
  7. 所以而不是测量[刷新] - &gt; [停止]时间并将其与PHP记录的数字进行比较,我测量显示的输出记录的输出 - 这将延迟测量减少到大部分只在PHP内(尽管Server /浏览器会起作用)。修改后的脚本无法运行(它在固定次数的迭代后终止),清除默认的php.ini缓冲,并在屏幕和计时文件中报告迭代计数。我用不同的$sleep个句点运行脚本来查看效果。最后的剧本:

    <?php
    date_default_timezone_set('America/Los_Angeles'); //Used by ob apparently
    ignore_user_abort(true); //Don't terminate script because user leaves
    set_time_limit(0); //Allow runaway script !danger !danger
    while (@ob_end_flush()) {}; //By default set on/4K in php.ini
    
    $start=microtime(true);
    
    $n=1000;
    $i=0;
    $sleep=100000;// 0.1s
    while(!connection_aborted() && $i<$n) {
        echo "\n[".++$i."]";flush();
        usleep($sleep);
    }
    
    $end=microtime(true);
    
    file_put_contents("timing.txt",
        "#\$sleep=".($sleep/1000000).
          "s\n/ s=$start / e=$end ($i) / d=".($end-$start)."\n",
        FILE_APPEND);
    ?>
    

    结果(连续多次运行,在Firefox中运行)

    # On-screen $i / start utime / end utime (current $i) / delta utime
    
    #$sleep=1s
    2 / s=1296251342.5729 / e=1296251346.5721 (4) / d=3.999242067337
    3 / s=1296251352.9094 / e=1296251357.91 (5) / d=5.000559091568
    #$sleep=0.1s
    11 / s=1296251157.982 / e=1296251159.2896 (13) / d=1.3075668811798
    8 / s=1296251167.5659 / e=1296251168.5709 (10) / d=1.0050280094147
    16 / s=1296251190.0493 / e=1296251191.8599 (18) / d=1.810576915741
    4 / s=1296251202.7471 / e=1296251203.3505 (6) / d=0.60339689254761
    16 / s=1296251724.5782 / e=1296251726.3882 (18) / d=1.8099851608276
    #$sleep=0.01s
    42 / s=1296251233.0498 / e=1296251233.5217 (44) / d=0.47195816040039
    62 / s=1296251260.4463 / e=1296251261.1336 (64) / d=0.68735003471375
    150 / s=1296251279.2656 / e=1296251280.901 (152) / d=1.6353850364685
    379 / s=1296252444.7587 / e=1296252449.0108 (394) / d=4.2521529197693
    #$sleep=0.001s
    337 / s=1296251293.4823 / e=1296251294.1515 (341) / d=0.66925406455994
    207 / s=1296251313.7312 / e=1296251314.1445 (211) / d=0.41328597068787
    792 / s=1296251324.5233 / e=1296251326.0915 (795) / d=1.5682451725006
    

    (Opera不显示数字,但显示大致匹配的最终数字)
    (Chrome在测试期间/之后不显示任何内容)
    (Safari在测试期间/之后不显示任何内容)
    (IE在测试期间/之后不显示任何内容)

    每行上的第一个数字表示按下[停止]后手动录制的屏幕上显示的数字。

    几点:

    • 您的停止点被量化为最接近的$sleep周期(上述脚本中的1 / 10s),因为脚本仅在每次迭代开始时检查,因为usleep方法存在一些变化不是一个完美的延迟。
    • 您正在使用的浏览器和服务器有所不同。 flush手册页注释“可能无法覆盖Web服务器的缓冲方案,并且不会影响浏览器中的任何客户端缓冲。”然后详细介绍服务器和客户端问题。 [服务器:WinXPsp3 / Apache 2.2.17 / PHP 5.3.3 客户端:WinXPsp3 / FireFox 3.6.13]

    <强>分析:

    除了0.001秒延迟之外,我们在[stop]和PHP捕获它(或Firefox或Apache报告)之间看到了2次迭代延迟。延迟为0.001秒时,它会有一点变化,平均值为~4次迭代或0.004s - 这可能接近检测速度阈值。

    当延迟时间为0.1秒或更长时,我们看到执行时间与$sleep * {记录的迭代}紧密匹配。

    当延迟时间低于0.1秒时,我们发现执行延迟大于睡眠时间。这可能来自检查客户端连接,递增$i,输出文本以及每次迭代刷新缓冲区的成本。执行时间和$i*$sleep之间的差异是非常线性的,表明完成这些任务需要大约0.001秒/迭代(0.01s睡眠时为0.0008,而0.001s睡眠时间为0.0010 - 可能是增加MALLOC /输出)。

答案 2 :(得分:6)

当您点击浏览器中的“停止”按钮时,您依赖connection_aborted()true,但没有证据表明您已经确认是这种情况。事实上,它不是。

您忘记了网络中“连接中止”检查的方式。应用程序(在这种情况下为php)在尝试写入管道之前不知道发生了什么。

关于the documentation for connection_abort()的第一条评论说:“为了检测脚本内部的断开连接,我们需要刷新缓冲区(只有当服务器尝试发送缓冲区内容时才会看到它连接断了)。“

所以我不相信你能以这种方式可靠地使用connection_abort()

请放心,microtime()正常工作。

答案 3 :(得分:4)

connection_aborted()只能在发送缓冲区时检测断开连接。但是flush()并不一定发送缓冲区。因此循环不断迭代,直到缓冲区被填满并且确实被刷新。

有关详细信息,请参阅指定功能的手册页。

答案 4 :(得分:0)

使用您的脚本开箱即用,在Ubuntu上无法正常运行,使用Chrome访问该页面。 循环只是继续,不得不重启Apache。 另一方面,在顶部添加ob_end_flush()可以解决该问题,而且计时器实际上是正确的。

ob_end_flush();
ignore_user_abort(true); 
set_time_limit(0);

$begin_time = microtime(true);

$elapsed_time = 0;

while(!connection_aborted()) {
    echo ' ';
    flush();
    usleep(1000000);
    error_log("looping");
}

$elapsed_time = microtime(true) - $begin_time;

$timer_seconds = $elapsed_time; 
error_log(var_export($timer_seconds, true));


$timer_seconds = $elapsed_time / 2; 
error_log(var_export($timer_seconds, true));

如果你运行它并查看错误日志,你会发现$ elpased_time在第一次运行时是正确的,不需要除以。根据你的代码行为的原因,我不知道,因为它甚至不能在我的机器上运行。

答案 5 :(得分:0)

这不是“问题”,而是“按设计”

这是http工作方式的一个功能。

当向客户端(浏览器)发送网页时,服务器“必须”发送内容长度标题。现在,除非从脚本中获取所有内容,否则无法知道内容的长度。

因此服务器将缓冲脚本输出,直到脚本完成。

这是变幻莫测的进入的地方。取决于服务器甚至同一服务器的不同版本,它可能会也可能不会检查客户端是否定期断开连接,如果是,那些检查可能会有所不同时间间隔。甚至可能会根据服务器的繁忙程度而改变。

PHP无法控制与客户端的连接,它只能询问服务器连接是否仍然存在。服务器可能会或可能不会说实话。因此脚本继续运行(在服务器可能不知道的时候)。

那么为什么在脚本的开头添加ob_end_flush()之后Mikushi的工作呢?

那就好了,因为它打开了另一个名为chunk transfer的http功能。这允许数据以块的形式发送,每个块都有一个特殊版本的内容长度标题(它实际上并不是说它只是发送下一个块长度)

使用Wireshark尝试Mikushi的脚本,您将看到编码,此处显示了一个示例

HTTP/1.1 200 OK
Date: Tue, 01 Feb 2011 11:52:35 GMT
Server: Apache/2.2.3 (CentOS)
X-Powered-By: PHP/5.1.6
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8


7 <== this is the content-length 
<pre>0

2 <== this is the content-length 
1

2 ditto ...
2

2
3

2
4

2
5

所以这意味着它是不可能的(是的Tomalak,你让我:))知道什么时候服务器将终止连接,因此为connection_aborted()返回true,直到你测试所涉及的实际服务器。因为每个人都不同。即使是网络浏览器也可能会做一些延迟实际关闭的事情,这可能会使问题更加混乱。

DC