【问题标题】:Echoing content sometimes takes a very long time回显内容有时需要很长时间
【发布时间】:2012-01-03 11:44:37
【问题描述】:

我有一个脚本,可以在一个字符串 ($content) 中构建我的网页,然后将其回显给用户。

我的脚本如下所示:

$time1= microtime(true);
$content = create_content();
$content_time=(microtime(true)-$time1)

$time = microtime(true);
echo $content;
$echo_time = (microtime(true)-$time);

现在 $content_time 总是低于 0.5 秒,所以没问题。然而,每天有几次 $echo_time 远高于 1 秒,甚至可以达到 15 秒。内容不是很大,大概10-20kb,而且发生的时间完全随机,所以不是在繁忙的时候,甚至在半夜发生。

有人知道那是什么吗?

编辑 该站点托管在(远程)专用服务器上,并且仅托管此站点。涉及到一个数据库,但就像我说的 $content_time 远低于 1 秒,所以这个函数所做的不能是延迟。

当我的网站时间超过某个值(比如说 5 秒)时,我会记录下来。甚至 Googlebots 有时似乎也会遇到这些问题,所以我认为他们不使用拨号连接 :)

【问题讨论】:

  • 我认为没有人能回答,有很多地方可以引起问题。首先 - 本地机器,或远程服务器,VPS 或专用或只是托管。需要详细信息...
  • 当时服务器可能有一些负载...
  • 作为@devdRew,您需要提供更多详细信息,例如创建内容的一些代码。是否有数据库,这是在 Web 服务器还是本地计算机上,等等。
  • 可能是您的服务器 ping 非常高。或者你正在使用拨号(那你怎么能在 SO 上发帖)?
  • 我在问题中添加了更多信息

标签: php performance


【解决方案1】:

让我们缩小问题的范围并考虑一些事情......

在问题中,您表示您正在回显 10-15kb。无论它如何缓冲到输出,这都是一个很大的数量——记住 php 是单线程的,一旦你刷新缓冲区,你必须等待所有输出通过 shell 或 HTTP 发生,然后脚本才能继续。它最终必须在继续回显之前刷新内部缓冲区。在没有 echo 的刷新开销的情况下享受美好时光

尝试更换

$time = microtime(true);
echo $content;
$echo_time = (microtime(true)-$time);

ob_start();
$time = microtime(true);
echo $content;
$echo_time = (microtime(true)-$time);
ob_clean();

这将回显到缓冲区,但实际上不会通过 HTTP 或其他方式将其吐出。这应该为您提供 echo 命令的“实时”时间,而无需担心发送缓冲区中的内容。

如果 echo_time 缩短,则您需要通过缓冲尽可能解决传输问题。

如果 echo_time 仍然很大,您需要开始深入研究 PHP C 代码。

无论哪种方式,您都更接近于找到您的问题和解决方案

【讨论】:

  • @Nin 你能尝试这个来缩小你的问题范围吗?
  • 不,我还没有弄清楚为什么会这样。问题是我无法重现。不过我会接受你的回答。
【解决方案2】:

来自http://wonko.com/post/seeing_poor_performance_using_phps_echo_statement_heres_why

This old bug report 可能会有所启发。简而言之,由于Nagle’s Algorithm 的方式会导致数据被缓冲以通过 TCP/IP 传输,因此使用 echo 向浏览器发送大字符串会导致可怕的性能。

解决方案?一个简单的三行函数,可以在回显之前将大字符串拆分成更小的块:

function echobig($string, $bufferSize = 8192) { 
    $splitString = str_split($string, $bufferSize);

    foreach($splitString as $chunk) { echo $chunk; }
}

玩转缓冲区大小,看看什么最适合您。我发现 8192 除了是一个不错的整数之外,似乎也是一个不错的尺寸。某些其他值也有效,但经过几分钟的修补后,我无法辨别出一种模式,而且显然有一些数学在起作用,我不想尝试弄清楚。

顺便说一句,当使用 PHP 的输出控制函数(ob_start() 和朋友)时,性能也会受到影响

在他尝试过的 OP 评论之后,我还在 PHP.net 上发现了以下内容,这表明 str_split 也可能浪费资源,并且可以使用以下代码进一步优化 echobig 函数:

function echobig($string, $bufferSize = 8192) {
  // suggest doing a test for Integer & positive bufferSize
  for ($chars=strlen($string)-1,$start=0;$start <= $chars;$start += $bufferSize) {
    echo substr($string,$start,$buffer_size);
  }
}

您是否尝试过使用 CLI 而不是通过 Apache 运行脚本?

【讨论】:

  • 我已经尝试过了,但它没有帮助。我仍然认为它的表现非常糟糕(2 秒以上)。
  • 您是否按照文章中的建议调整了缓冲区大小?
  • 不,我没有摆弄缓冲区大小,首先因为我自己无法重现这个,我只能在我的日志中看到它。但主要是因为把它改成这个(从正常的 echo $content 开始)没有效果,所以我真的不认为改变这个值就可以了。
【解决方案3】:

您可以使用输出缓冲区更好地做到这一点。在基本级别上,您使用ob_start() 开始写入输出缓冲区,然后使用ob_end_flush() 将其推送到客户端。以下是 php.net 对ob_start() 的评价:

此函数将打开输出缓冲。当输出缓冲处于活动状态时,不会从脚本发送输出(除了标题),而是将输出存储在内部缓冲区中。 这个内部缓冲区的内容可以使用ob_get_contents() 复制到一个字符串变量中。要输出存储在内部缓冲区中的内容,请使用ob_end_flush()

【讨论】:

  • 这主要在你做多个echo语句时有用,但在这种情况下只有1个echo,所以缓冲已经在脚本中完成了。
  • 是的,但缓冲区也允许压缩,这会显着降低带宽。您还可以重用缓冲区或缓存它。这真的归结为 OP 需要完成的任务。
  • 但这不可能是0.0007s(正常)和13s(峰值)之间的区别
【解决方案4】:

我过去遇到过与您非常相似的问题。我发现这个问题可能是由慢速客户端引起的。如果客户端获取页面的一半然后挂起,php 将等待客户端准备好,然后发送其余内容。所以这对你来说可能不是问题。

更新:

您可以尝试在您的服务器上使用以下脚本来检查这一点。这个脚本放在你的服务器上并称之为 echo.php:

<?php
$time_start = time();
echo str_repeat("a", 200000);
echo "\nThis script took: " . (time() - $time_start) . " sec";

然后使用此脚本获取它(将 example.com 更改为您的域):

<?php
$fp = fsockopen("example.com", 80, $errno, $errstr, 30);
if (!$fp) {
    echo "$errstr ($errno)<br />\n";
} else {
    $out = "GET /echo.php HTTP/1.1\r\n";
    $out .= "Host: example.com\r\n";
    $out .= "Connection: Close\r\n\r\n";
    fwrite($fp, $out);
    while (!feof($fp)) {
        echo fgets($fp, 5000);
        sleep(1);
    }
    fclose($fp);
}

echo.php 运行了 27 秒。当我删除行 sleep(1) 时,echo.php 只需 2 秒即可运行。

【讨论】:

  • 我也考虑过这一点(并且可能是唯一可能的),但我确实看到 Google bot 遭受了同样的问题,而且我认为它们的连接速度很快。但也许他们也有一些小问题......
【解决方案5】:

如果不知道 create_content() 函数的主体就不可能告诉你原因,我建议你直接在这个函数中添加更多的“时间记录”函数。使包含的代码越来越少,您最终会找到导致延迟的行。 了解具体线路将帮助您了解问题(数据库、机器负载、与外部服务的连接问题……)。

【讨论】:

  • create_content() 函数在 0.5 秒内表现良好,因此该函数不是问题。它的$echo_time 就是问题所在。 create_content() 函数仅在示例代码中显示 $content 是在某处创建的并且执行良好。
【解决方案6】:

您的脚本中是否有任何 while() 或 for() 循环?如果是这样,您应该检查这些值是否与任何内容冲突,有时我自己忘记了这些,我的脚本也会运行大约 30 秒。

【讨论】:

    【解决方案7】:

    我的猜测是访问这么大的字符串的行为在多次使用中占用了大量的内存。因为 PHP 是垃圾收集器,所以内存被占用,直到垃圾收集器运行,然后才被释放。 我的猜测是,在字符串变量中存储内容的多个请求会导致易失性内存 (RAM) 快速填满。然后每天有几次你开始达到限制,导致加载时间变慢。垃圾收集器来袭,一切恢复正常。

    【讨论】:

    • 服务器有 16GB 的内存,几乎总是有 8GB 空闲(非活动)。除此之外,问题不在于生成字符串,而只是它的回显。
    【解决方案8】:

    如果这是专用服务器 - 请登录到控制台并查看生成内容时哪个进程使用大量 cpu 时间。当我们看不到代码时,很难说。也许您只需要数据库中的一些索引,或者您应该删除一些索引。

    您还可以检查 httpd 和 mysqld 日志文件。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-09-07
      • 2020-08-26
      • 2014-10-09
      • 2012-11-26
      • 2019-12-27
      • 2017-10-22
      • 2020-11-15
      相关资源
      最近更新 更多