Press "Enter" to skip to content

加速PHP的ECHO

你也许注意到过, 在PHP中使用ECHO输出大段字符串的时候, 执行时间会明显的长, 也就会有朋友认为PHP的ECHO性能很差.
我在之前的文章中, 已经解释过了原因, 也希望能纠正"PHP的ECHO性能差"的这个误会.
然而之前的文章, 也仅仅是给出了原因, 并没有介绍如何避免这个问题, 在今天公司内的某个产品线(Apache with PHP)发现了一个问题, 有用户在短时间内大量发起下载请求, 导致http连接数和数据库连接数剧增,
而数据库连接数剧增的原因是因为数据库的连接是单列模式, 一直到请求处理结束, 才会释放数据库链接. 这样就有了一个问题, 如果请求处理时间过长, 就会造成大量的数据库链接存在.
而这个用户的网速很慢, 这也就意味着, ECHO的"性能"很差~, 下载时间很长~. 如下图所示:

ECHO执行示意图

这也就引出了今天我要谈的这个问题, 如何让ECHO变快, 让PHP的请求处理过程, 尽快结束...
我们知道, 之所以ECHO慢, 是在等待"写数据"成功返回, 那么一个比较简单的办法, 就是打开输出缓存,
编辑php.ini

 output_buffering = 4096 //bytes

当然, 你也可以在脚本中, 显示的调用ob_start():

ob_start();
echo $huge_string;
//其他的逻辑.
ob_end_flush();

这里, 有一个要注意的地方, ob_start将会开辟一块4096大小的buffer, 所以, 如果huge_string大于4096, 将不会起到加速的作用.
现在, 我们的ECHO就会"立即"执行成功, 返回. 因为数据暂时写到了我们的输出缓存当中. 如果buffer足够大, 那么内容会等到脚本的最后, 才一次性发送给客户端(严格来说, 是发送给WebServer).
但这样并不能解决我们今天遇到的这个问题, 因为这些数据到最后, 还是需要PHP去把它们发送给客户端(此时不考虑WebServer的Output buffer), 这个过程不结束, 请求不会关闭, PHP也不会执行DB的析构函数~
那么, 既然做梦, 那就再做大点, 我们可以使用Apache的输出缓存. 也就是改变成如下的执行流程:

加速ECHO示意图

假设, 我们的PHP要输出100K的数据, 那么, 我们Apache的的输出缓存就必须大于100K, 否则当Apache的输出缓存满了以后, 就会真正的发送给客户端, 而这个过程中, 当时执行的ECHO就会阻塞等待.
那么, 如何修改Apache的输出缓存呢? 我们可以在apache的配置文件中, 使用SendBufferSize配置指令:

    SendBufferSize    4096 //注意是byte

具体的SendBufferSize的说明, 参看http://httpd.apache.org/docs/2.0/en/mod/mpm_common.html#sendbuffersize
注: 其他的Webserver with php-cgi的模式, 请翻阅相关Webserver的手册, 寻找类似配置.
现在, PHP的ECHO, 将直接把内容交给Apache, PHP在执行完成后, 不再等待内容发送给客户端完成, 而直接退出. 而内容会在PHP处理完成以后, 由Apache发送给客户端. 从而加速了ECHO的执行效率.
废话一句: printf, print, file_put_contents("php://output")...等等, 和ECHO都是一样的.
最后要说明, 这样做, 只是把原来ECHO的等待时间, 转移给了Apache, 并没有真正的减少客户端获取到内容的时间. 它只是加速了PHP的处理过程, 提前了PHP的退出时机, 从而能减少PHP对资源的占用时间, 间接增加资源的占用率.

36 Comments

  1. Poker Asia
    Poker Asia March 4, 2022

    GembalaPoker Situs Resmi Judi Live Idn Poker 88 atau Poker Online Indonesia Serta Judi Poker Deposit Pulsa Tanpa Potongan Terbaik Terbesar Terpercaya

  2. 迅速に対応していただき、また包装もキレイでとても良かったです。
    また、何かの機会がありましたら、利用させていただきます。
    【送料無料】グッチ パスケースをセール価格で販売中♪グッチ パスケース グッチスウィング トレインパスケース 354500 ライトブルー レザー 新品 未使用 カードケース トレードマーク グッチ
    通勤用
    同じ商品の定期入れを落としてしまいました。定期券・いろんなカードが入っていて、気に入って使用していたのですが、落とし物でどこにも届いておらず、残念です(><@)
    また、色違いで購入しました。
    色は迷いましたが、キレイなブルーに決めました。
    これで一気進展、愛用し頑張りたいと思います。

  3. producent reklamy lubin
    producent reklamy lubin October 28, 2016

    It’s remarkable to go to see this site and reading the views of all friends
    concerning this paragraph, while I am also eager of getting knowledge.

  4. zerodeng
    zerodeng October 9, 2016

    你好,我想问一下,如果我在不使用ob_start()的情况下使用ob_end_flush()的话,会有什么效果?

  5. CharmainYBussone
    CharmainYBussone July 16, 2015

    Hello, I want to subscribe for this web site to take newest updates, therefore where can i do it please help out.

    • 熊起飞了
      熊起飞了 September 3, 2019

      maybe you can suggest him to develop this function

  6. xingzhef
    xingzhef September 19, 2014

    鸟哥
    浏览器接收到用户请求,然后web server交给php引擎处理php代码,处理完以后将数据返回给web server,由web server返回给浏览器,应该本来就是这个流程吧。
    也就是说即使是慢,也应该是慢在apache给浏览器交互的时候,怎么会是echo直接给浏览器呢

  7. 向前
    向前 October 31, 2011

    mvc的view之前关闭数据库连接的做法,
    要根据数据库连接的生命周期自行调整,
    比如fastcgi大循环前/后做开启/持久操作的情况、
    持久连接的情况就不一定合适,
    多一轮关闭和创建数据库连接
    或许比一个长时间的echo操作代价更大。
    统一 php 和 webserver 的 buffer 应该是必须的做法,
    否则之间的换算就有损耗。
    最后, tcp 包的问题了,
    这个太底层,不懂,即使客户端和服务端之间能协商每包
    大小,貌似要考虑丢包率,大包貌似容易丢、损(这个,
    从生活常识理解的,搬一张床不如连续搬多张容易磕着,
    101010貌似越长越容易丢……),
    貌似该由ISP和思科之类的基础厂商提供数据,
    包大小和传输效率在不同网络环境的取舍值,
    然后server和client协商,
    呃……但是如何分别客户端是电话线adsl来的还是
    光纤来的……这需求粒度太细了,
    感觉无论开多大只要webserver把缓存放内存,都该能接受,
    但是开太大了都逼apache放到硬盘缓存(应该没设置到这么
    大的人),就算了吧。
    P.S
    压缩输出,本身就是时间紧迫(网速慢时)时采用的
    空间换时间策略,要么等待客户端慢慢来,
    要么你的服务端自己耗cpu压缩一下换取带宽。

  8. 神奇的Fastcgi_Finish_Request
    神奇的Fastcgi_Finish_Request September 25, 2011

    […] 关闭连接. 但是不会结束PHP的运行. 相比调用flush, 或者我之前介绍的加速你的Echo来说, […]

  9. windyrobin
    windyrobin June 23, 2011

    SendBufferSize Directive
    The server will set the TCP send buffer size to the number of bytes specified. Very useful to increase past standard OS defaults on high speed high latency (i.e., 100ms or so, such as transcontinental fast pipes).
    If set to the value of 0, the server will use the OS default.
    比如在echo 500k的场景下,我们设置这个参数为4096kB
    个人理解, 大约会调用 500/4 次 linux send 函数,
    其实我们知道:send 返回时只代表把数据传递给料tcp/ip 堆栈内核层,并不是代表已经通过网络发给客户端了,
    所以,调节这个参数从理论上不应该有太大影响的 …
    而且 一个tcp 包 大小 约为 1152Byte ,所以这个大小也是不宜过大的…

  10. Azhun
    Azhun June 9, 2011

    研究得很深入,过来学习。

  11. qai41
    qai41 March 26, 2011

    在echo前关闭数据库连接,那么网速忙就不会对数据库有影响了.

  12. ryan
    ryan March 8, 2011

    我刚看了,implicit_flush = Off

  13. 雪候鸟
    雪候鸟 March 8, 2011

    @ryan 是不是打开了implicit_flush?

  14. ryan
    ryan March 7, 2011

    在我这里无法复现这个问题
    我这里echo 200K的字节,即使修改了SendBufferSize,前后所用的时间也是将近200ms左右,没有明显差异。
    代码如下:
    $str = ‘xxxx’;
    $str .= $str;
    $str .= $str;
    $str .= $str;
    $str .= $str;
    $str .= $str;
    $str .= $str;
    $str .= $str;
    //ob_start();
    echo $str;
    //ob_end_flush();

  15. iterse
    iterse February 16, 2011

    处理的过程,很巧妙,顶!

  16. gsid
    gsid February 13, 2011

    技术流,技术大牛:)

  17. xLight
    xLight February 13, 2011

    也是个好办法,解决问题就是好办法。
    何况这只是改改参数,还不是去调系统内核呢。

  18. c
    c February 13, 2011

    这种问题应该从架构/框架上解决,而不是用一些trick来掩盖架构上的设计错误。
    任何echo之前,都应该先做关闭数据库等操作。也就是说,在web环境里,echo等输出操作只应该存在于view,数据库连接只应该存在于load view之前。
    $data=db->select();
    assign(‘data’, $data);
    unset($db);
    load_view();

Comments are closed.