PHP's Output Buffering

  Ilia Alshanetsky        2011-12-08 10:20:32       4,231        0    

While profiling our application I came across a a rather strange memory usage by the ob_start() function. We do use ob_start() quite a bit to defer output of data, which is a common thing in many applications. What was unusual is that 16 calls to ob_start() up chewing through almost 700kb of memory, given that the data being buffered rarely exceeds 1-2kb, this was quite unusual.

I started looking at the C code of the ob_start() function and found this interesting bit of code inside php_start_ob_buffer()
initial_size = 40*1024;
block_size = 10*1024;


Which directs PHP to pre-allocate 40kb of data for each ob_start() call and when this proves to be insufficient, increase by 10kb each time. Ouch!

PHP does allow you to say how much memory ob_start() can use, via 2nd parameter to the function. However, if you exceed that size, PHP will promptly flush the captured data to screen, which means that unless you are really good at predicting your buffer sizes or vastly overestimate, there is a risk that the data will be dumped to screen by PHP if you use this option.

Since I am not really good at guessing, I've decided to make a small, backwards compatible tweak to PHP's code that allow specification of custom buffer sizes, but allow the buffer size to be increased if the initial buffer size proves to be insufficient, ensuring that the data can be safely buffered. This functionality is implemented through a change (see patch below) to the 1st parameter of the ob_start() function, which normally is used to provide the callback function. With the patch in place the parameter, can be a number, which defines the desired buffer size. With the patch, ob_start(1024) means that the 1kb buffer should be used and when it is exceed keep allocating 1kb at a time to allow for additional data to be stored. This solution does mean you cannot use custom, resizable buffer sizes with a callback function, however it does provider a backwards (PHP API wise) compatible way of implementing the functionality in PHP 5.2 and 5.3.

Here is a simple before & after example:
PHP:

<?php

     ob_start
(null1024);
     echo 
str_repeat("a"1500);
     
var_dump(strlen(ob_get_clean()));

?>


will print 0, since buffer is exceeded and is flushed to screen

PHP:

<?php

     ob_start
(1024);
     echo 
str_repeat("a"1500);
     
var_dump(strlen(ob_get_clean()));

?>


will print 1500, buffer was exceeded and subsequently increased, allowing data to remain in the buffer

CODE:
Index: main/output.c
===================================================================
--- main/output.c (revision 320624)
+++ main/output.c (working copy)
@@ -155,10 +155,14 @@
  initial_size = (chunk_size*3/2);
  block_size = chunk_size/2;
  } else {
- initial_size = 40*1024;
- block_size = 10*1024;
+ if (output_handler && Z_TYPE_P(output_handler) == IS_LONG && !chunk_size) {
+ initial_size = block_size = Z_LVAL_P(output_handler);
+ } else {
+ initial_size = 40*1024;
+ block_size = 10*1024;
+ }
  }
- return php_ob_init(initial_size, block_size, output_handler, chunk_size, erase TSRMLS_CC);
+ return php_ob_init(initial_size, block_size, (output_handler && Z_TYPE_P(output_handler) != IS_LONG ? output_handler : NULL), chunk_size, erase TSRMLS_CC);
 }
 /* }}} */

Source: http://ilia.ws/archives/244-PHPs-Output-Buffering.html

PHP  MEMORY  OB_START()  SOURCE  40KB 

       

  RELATED


  0 COMMENT


No comment for this article.



  RANDOM FUN

Senior software engineer