14

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

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需要除以二。也许我误解了什么?

谢谢大家的帮助

更新

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

4

6 回答 6

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 秒。


推论和结论:

1) 当点击 STOP 按钮时,脚本实际上并没有停止,而是在点击 2-4 秒后停止。因此,浏览器中出现的迭代次数更多。

2) 迭代次数与执行所需的秒数相同,如输出文件所示。

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


笔记:

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

于 2011-01-26T11:58:00.843 回答
9

摘要:(当我测试各种途径时,这篇文章变得史诗般)

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

史诗般的帖子:

从 [Refresh] 或URL-submit计算您的执行时间并不完全是一个准确的起点 - 首先可能需要任何数量的步骤,并且可能会增加延迟:

  1. 需要 DNS 查找(有 TCP 开销)
  2. 与服务器建立 TCP 连接
  3. Web 服务器创建线程或子线程
  4. Web Server 决定如何处理请求
  5. PHP 可能需要启动
  6. PHP 可能需要将您的源代码转换为操作码

因此,我没有测量 [Refresh] -> [Stop] 时间并将其与 PHP 记录的数字进行比较,而是测量了显示的输出记录的输出- 这将延迟测量减少到大部分仅在 PHP 内(尽管服务器/浏览器会起作用)。修改后的脚本不能跑掉(它在固定数量的迭代后终止),清除默认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/10 秒),因为脚本仅在每次迭代开始时检查,存在一些变化,因为该usleep方法不是完美的延迟。
  • 您使用的浏览器和服务器会有所不同。刷新手册页指出“可能无法覆盖 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.004 秒 - 这可能接近检测速度阈值。

当延迟时间为 0.1 秒或以上时,我们会看到执行时间与$sleep* {记录的迭代}非常匹配。

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

于 2011-01-28T23:03:06.880 回答
6

您依赖的connection_aborted()true您在浏览器中点击“停止”按钮的那一刻,但没有显示任何证据表明您已经验证了这种情况。事实上,事实并非如此。

您已经忘记了网络中的“连接中止”检查是如何发生的。应用程序(在本例中为 php)在尝试写入管道之前不知道发生了什么。

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

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

放心,microtime()工作正常。

于 2011-01-22T00:37:32.417 回答
4

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

有关详细信息,请参阅命名函数的手册页。

于 2011-01-22T00:41:15.480 回答
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 在第一次运行时是正确的,也不需要除以。至于你的代码为什么会这样,我不知道,因为它甚至不能在我的机器上运行。

于 2011-01-26T20:45:27.060 回答
0

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

它是 http 工作方式的一个函数。

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

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

这就是变幻莫测的地方。根据服务器甚至同一服务器的不同版本,它可能会或可能不会定期检查客户端是否已断开连接,如果确实如此,这些检查可以在不同的时间间隔进行。这甚至可能会根据服务器的繁忙程度而改变。

PHP 无法控制与客户端的连接,它只能询问服务器连接是否仍然存在。服务器可能会或可能不会告诉它真相。所以脚本继续运行(当时服务器可能不知道)。

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

那是因为它开启了 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,直到您测试所涉及的实际服务器。因为每一个都不一样。甚至网络浏览器也可能会做一些延迟实际关闭的事情,这可能会进一步混淆问题。

直流

于 2011-02-01T12:05:26.613 回答