PHP echo长字符串的效率问题
May/19th 2010

    偶然看到Volcano发表在http://www.ooso.net/archives/517/的博文,说PHP显示长字符串的时候会导致PHP执行时间超长问题。

    正好前段时间研究过HTTP协议,想到可能与HTTP的Transfer-Encoding(数据传输模式)有关。

 

    于是尝试重现Volcano遇到的问题。

(此时,apache没有开gzip压缩)

http://localhost/chunked.php:

<?php
$_start = microtime(true);
echo str_pad('___', 500*1024, "_"); //构造一个500KB的字符串
$_end = microtime(true);
$_time = $_end - $_start;
echo '<br>';
echo intval($_time*1000).'ms';
?>

    用firefox打开,显示php平均执行时间在450ms左右。用firebug查看本次请求,显示数据是500KB,用时为700ms左右,传输模式(Transfer-Encoding)为chunked

    由于是本地,所以网速肯定比实际要快。于是我写了个脚本,来模拟网速慢的时候:

test.php:

<?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压缩,肯定是以强制缓存输出为必要条件的。)。

 

    当然,这只是我根据测试结果的一些推测,如果有不妥的地方,请大家指出



8110 read 18 comment(s)
#1
宇博   2010年05月20号 13:19       回复
据说 sleep 很占资源...可能Apache一直考虑国外那快快的网速吧才这么做....
#2
宇博   2010年05月20号 13:20       回复
什么情况??怎么“error4”,并且发不出去,用IE才能发出去....
#3
longbill   2010年05月20号 13:24       回复
@宇博 呃。。貌似是cookie的问题。我现在关闭了error4的检查,以后就不会出现error4了。
#4
longbill   2010年05月20号 13:26       回复
@宇博 apache应该是考虑到可能考虑到php在输出那么长的东西的时候执行时间也会很长,与其让浏览器干等,还不如先丢点东西过去。
#5
endto   2010年05月20号 13:43       回复
期待endcms发布……
#6
endto   2010年05月20号 13:43       回复
@endto 

测试程序
#7
endto   2010年05月20号 13:44       回复
@longbill 

测试评论
#8
刘春龙   2010年05月20号 13:46       回复
@endto 呃………
#9
FlyingHail   2010年06月01号 10:53       回复
分析的是对的,但是可以看出来,其实还有一个办法可以解决chunked问题,就是定义上Content-Length:<?php$_start = microtime(true);ob_start();echo str_pad('___', 500*1024, "_"); //构造一个500KB的字符串$_end = microtime(true);$_time = $_end - $_start;echo '<br>';echo ($_time*1000).'ms';header("Content-Length: " . ob_get_length());这样直接运行的速度会由400ms+变成类似于开启gzip的 12ms+,当然你那个测试程序肯定不行,因为长度非常大,又没有压缩,1个字节停1毫秒肯定囧了...
#10
longbill   2010年06月01号 12:08       回复
@FlyingHail 最近发现可能和php.ini里面的output_buffering有关。
#11
FlyingHail   2010年06月01号 12:12       回复
@longbill output_buffering 应该是会影响到chuncked每一个的大小吧,大概不超过output_buffering的大小就不会速度影响很大,不过显然一般不会把这个定义的很大,可以测试看看...
#12
mx1700   2010年06月07号 09:11       回复
我用了你写的 文件管理器 ,感觉很好用,也很强大。
最近换了Chrome浏览器,管理器的编辑页面 不能修改,按任何键都不会输出内容。希望博主能修复这个问题。谢谢。
#13
宇博   2010年06月20号 22:10       回复
报告博主。。。我的台湾网友说。。你的CMS管理器啥都好,就是繁体中文版的翻译太差劲,简直没有翻译。报告完毕。。。。
#14
宇博   2010年06月21号 13:39       回复
再报告一个....“刘春龙”貌似已经被屏蔽,在谷歌搜索会“该页无法显示”...
#15
宇博   2010年06月22号 12:35       回复
为什么博客里多了很多相同的文章。。。
#16
kookxiang   2010年07月17号 13:03       回复
讲了这么久,有提速的方法么……
#17
longbill   2010年07月17号 13:11       回复
@kookxiang 当然有。配置apache输出gzip就可以了啊。
添加新的评论
称呼:*
邮件:*
网站:
内容:

Copyright © Longbill 2008-2024 , Designed by EndTo , Powered by EndCMS