// Echo the output string echo implode(‘’, $output); } ?> This script demonstrates four versions of a method (v1, v2, v3, and v4), each of which performs the same task, but with progressive efficiency. The script calls a method called doBenchmark(), which runs each version of the function under benchmark conditions using the Benchmark/Iterate.php class for MAX_RUN times. The result of each benchmark is printed using a printf() state- ment. Following is a sample of output from the script: v1 ran 100 times where average exec time 1.60087 ms v2 ran 100 times where average exec time 1.05392 ms v3 ran 100 times where average exec time 0.55139 ms v4 ran 100 times where average exec time 0.27371 ms Here, you can see that v1() ran the slowest. Lets look at what v1() does. This function receives an array as an argument and loops through each element of the array using a for loop. Notice that the loop uses the sizeof() function to deter- mine the size of the array. The sizeof() function is called each time the loop iter- ates. This degrades the speed of the loop substantially, as function calls are expensive in terms of execution time. Inside the loop, only an echo() function prints an HTML comment statement. Now let’s look at the v2() function. This function does the same task of v1(), but it is a bit faster (0.54695 ms), as it stores the size of the $myArray in the $max variable outside the loop and therefore avoids the penalty of calling sizeof() for each iteration. Looking at the v3() version of the same function, you can see that this version is significantly faster than v2() because it not only does what v2() does, it also removes from the loop the iterative call to the expensive I/O function echo() by storing the output in a variable called $output. This improves its performance greatly, as it uses only a single echo() call to print the contents stored in $output as the last statement of the function. Finally, v4() does everything v3() does except that instead of storing output in a $output string variable that is appended using the dot operator (as done in v4), it uses a faster array_push() function to store output as a series of sequential array elements in $output array. Finally, it appends the contents of the $output array using the implode() function, which is passed to a single echo() function. This appears to be the fastest of the four implementations. Chapter 21: Speeding Up PHP Applications 721 28 549669 ch21.qxd 4/4/03 9:27 AM Page 721 How do you learn to make such improvements to your own code? The simple answer is experimentation and lots of practice. Study PHP’s built-in functions in great detail, as they are faster than anything you will write in PHP. Use of built-in functions in comparable situations can improve your code speed significantly. For example, consider this listing: function v5($myArray = null){ echo “<! ”, implode(“ > <! ”, $myArray), “ > “; } Stress-testing your PHP applications using ApacheBench The Apache server comes with a tool called ApacheBench (ab), which is installed by default in the bin directory of your Apache installation directory. By using this nifty tool, you can stress-test your application to see how it behaves under heavy load conditions. Make an estimate of how many requests you want your application to be able to service from your Web server. Write it down in a goal statement such as “I wish to service N requests per second.” Restart your Web server and from a system other than the Web server, run the ab command as follows: ./ab -n number_of_total_requests \ -c number_of_simultaneous_requests \ http://your_web_server/your_php_app.php For example: ./ab -n 1000 -c 50 http://www.domain.com/myapp.php The ApacheBench tool will make 50 concurrent requests, and a total of 1,000 requests. Sample output is shown here: Server Software: Apache/2.0.16 Server Hostname: localhost Server Port: 80 Document Path: /myapp.php Document Length: 1311 bytes Concurrency Level: 50 Time taken for tests: 8.794 seconds Complete requests: 1000 Failed requests: 0 722 Part VI: Tuning and Securing PHP Applications 28 549669 ch21.qxd 4/4/03 9:27 AM Page 722 Total transferred: 1754000 bytes HTML transferred: 1311000 bytes Requests per second: 113.71 Transfer rate: 199.45 kb/s received Connection Times (ms) min avg max Connect: 0 0 5 Processing: 111 427 550 Total: 111 427 555 Notice that Requests per second is 113.71 for accessing the myapp.php PHP script. Change the concurrent request count to a higher number and see how the server handles additional concurrent load. Tune your application as finely as possible using the techniques discussed ear- lier. You might have to also tune Apache using MaxClients, ThreadsPerChild, MaxThreadsPerChild, and so on, based on your MPM module choice in httpd.conf. Visit www.apache.org for in-depth documentation on Apache, including modules and third-party applications to improve performance. If you make changes to the Apache configuration file (httpd.conf), make sure you restart Apache, and apply the same benchmark tests by using ab as before. You should see your Requests per second increase or decrease based on the num- bers you try. As you tweak the numbers by changing the directive values, make sure you record the values and the performance so that you can determine the best setting for you. Buffering Your PHP Application Output Once you are sure that you have optimized your application to the best of your abilities, it is time to consider other techniques, such as output buffering. Output buffering is very effective for scripts that use numerous I/O functions such as echo(), print, printf(), and so on. If you use these functions often, you might find output buffering to be a speed booster. For example, Listing 21-4 shows a script called buffer.php that benchmarks (using the PEAR Benchmark/Timer discussed ear- lier in the section, “ Benchmarking your code”) a function called doSomething(), which prints the ‘x’ character in a loop, using the echo() function. Chapter 21: Speeding Up PHP Applications 723 28 549669 ch21.qxd 4/4/03 9:27 AM Page 723 Listing 21-4: buffer.php <?php define(MAX, 1024 * 10); // If you have installed PEAR packages in a different // directory than %DocumentRoot%/pear change the // setting below. $PEAR_DIR = $_SERVER[‘DOCUMENT_ROOT’] . ‘/pear’ ; $PATH = $PEAR_DIR; ini_set( ‘include_path’, ‘:’ . $PATH . ‘:’ . ini_get(‘include_path’)); require_once ‘Benchmark/Timer.php’; $timer = new Benchmark_Timer(); $kb = MAX / 1024; // No output buffering $timer->start(); doSomething(); $timer->stop(); printf(“Buffer: OFF Size: %d KB Time elapsed: %.3f<br>”, $kb, $timer->timeElapsed()); // Enable output buffering ob_start(); $timer->start(); doSomething(); $timer->stop(); printf(“Buffer: ON Size: %d KB Time elapsed: %.3f<br>” , $kb, $timer->timeElapsed()); exit; function doSomething() { $output = ‘’; 724 Part VI: Tuning and Securing PHP Applications 28 549669 ch21.qxd 4/4/03 9:27 AM Page 724 for($i=0;$i<=MAX;$i++) { echo ‘x’; } } ?> The first call to doSomething() in the script is made without output buffering, whereas the second call is made after output buffering is enabled using the ob_start() function. Sample benchmark results of the two calls to doSomething() are shown here: xxxxxxxxxxxxxx [ shortened for brevity] xxxxxxxxxxxxxxxxxxxxx Buffer: OFF Size: 10 KB Time elapsed: 10.939 xxxxxxxxxxxxxx [ shortened for brevity] xxxxxxxxxxxxxxxxxxxxx Buffer: ON Size: 10 KB Time elapsed: 0.071 As you can see, the unbuffered call to doSomething(), which uses echo() in a loop, required a significant amount of time vs. the buffered call. This means that ob_start() is an excellent choice to improve performance of this script. Therefore, whenever it is possible for you to enable output buffering, try it and measure the performance gain. If the gain is significant, you can use ob_start() in your code. Compressing Your PHP Application Output You can also compress your PHP-generated HTML output or image output using GZIP compression. You must enable GZIP compression while compiling PHP. This is done using the option with-zlib-dir=/usr/local/lib in the source code con- figuration step. Not all Web browsers can handle compressed output. Therefore, this method might not be applicable for all situations.You can take advantage of compression when you know that the Web browsers used by your visitors support compression. Because Microsoft Internet Explorer is a popular Web browser that supports compression, it is often worth trying. Chapter 21: Speeding Up PHP Applications 725 28 549669 ch21.qxd 4/4/03 9:27 AM Page 725 . 80 Document Path: /myapp .php Document Length: 1311 bytes Concurrency Level: 50 Time taken for tests: 8.794 seconds Complete requests: 1000 Failed requests: 0 722 Part VI: Tuning and Securing PHP. number_of_simultaneous_requests http://your_web_server/your _php_ app .php For example: ./ab -n 1000 -c 50 http://www.domain.com/myapp .php The ApacheBench tool will make 50 concurrent requests, and. using the echo() function. Chapter 21: Speeding Up PHP Applications 723 28 549669 ch21.qxd 4/4/03 9:27 AM Page 723 Listing 21-4: buffer .php < ?php define(MAX, 1024 * 10); // If you have installed