偶然看到Volcano发表在http://www.ooso.net/archives/517/的博文,说PHP显示长字符串的时候会导致PHP执行时间超长问题。
正好前段时间研究过HTTP协议,想到可能与HTTP的Transfer-Encoding(数据传输模式)有关。
于是尝试重现Volcano遇到的问题。
(此时,apache没有开gzip压缩)
http://localhost/chunked.php:
$_start = microtime(true);
echo str_pad('___', 500*1024, "_"); //构造一个500KB的字符串
$_end = microtime(true);
$_time = $_end - $_start;
echo '
';
echo intval($_time*1000).'ms';
?>
用firefox打开,显示php平均执行时间在450ms左右。用firebug查看本次请求,显示数据是500KB,用时为700ms左右,传输模式(Transfer-Encoding)为chunked。
由于是本地,所以网速肯定比实际要快。于是我写了个脚本,来模拟网速慢的时候:
test.php:
get_lrc("http://localhost/chunked.php");
function get_lrc($url)
{
$url = parse_url($url);
if($fp = @fsockopen($url['host'],empty($url['port'])?80:$url['port'],$error))
{
fputs($fp,"GET ".(empty($url['path'])?'/':$url['path'])." HTTP/1.1\r\n");
fputs($fp,"Host:".$url['host']."\r\n");
fputs($fp,"Accept-Encoding: gzip,deflate\r\n\r\n");
while(!feof($fp))
{
echo fread($fp,1);
usleep(1); //每读取1个字节,暂停执行1微妙(1/1000毫秒)
}
fclose($fp);
}
else echo 'connect failed';
}
?>
这个程序在本地命令行模式下运行。显示php执行时间在24000ms左右。24秒啊!!!
然后打开apache 的gzip压缩。详细方法大家google一下,很多。
再次用firefox打开http://localhost/chunked.php,显示的php执行时间在12ms左右。用firebug查看,没有Transfer-Encoding: chunked。并且Content-Encoding: gzip。数据总量为545B。
再执行test.php,立即显示了一堆http头信息(其中也是没有chunked,有gzip),正文为乱码(因为gzip压缩过了)。人工估计运行时间也是非常短。
为什么会这样呢?
首先要说下chunked传输模式。
一般如果传输模式不是chunked,服务器返回的http头信息里面有一项为Content-Length,表示此次请求返回的正文内容长度。浏览器可以根据这个长度来取内容。
但是有的时候,需要在完全输出内容之前就要把内容传给浏览器,或者是刚开始不知道内容有多长。这个时候就要用到chunked模式。 在这个模式下面,http请求的正文会被切割成若干个段,每段开始有一个十六进制的数字表示本段的长度。长度数字和段落后面均以\r\n结尾。
对于php的输出,貌似apache采取的策略是小段输出直接传输,大段输出就切割成chunked分段。在chunked分段没有传输完成之前,apache和php一直保持连接状态。
也就是说,如果php的输出字符串比较小,那么apache会把这些数据暂存,等到php执行完了之后再发给浏览器。而当php输出大段字符的时候,apache就不会缓存输出,直接把输出丢给浏览器,而且在此过程中会暂时停止php的执行!
这种情况看起来是挺吓人的,网速居然影响了php的效率! 但是仔细想想,php只是暂停执行而已,可能和sleep的效果差不多。不过由此带来的性能影响我还没测试过。 要想避免这种情况很好办,只需要强制apache每次都缓存php的输出即可(具体设置我还没发现,不过如果开启apache的gzip压缩,肯定是以强制缓存输出为必要条件的。)。
当然,这只是我根据测试结果的一些推测,如果有不妥的地方,请大家指出。
7421 read 18 comment(s)