In November 2006, PHP V5.2 was released with many new features and bug fixes. It obsoletes the 5.1 release and is a recommended upgrade for any PHP V5 users. My favorite lab environment -- Windows®, Apache, MySQL, PHP (WAMP) -- is rolled into a new package for V5.2 already (see Resources). You will find an application there that will set up PHP V5.2, MySQL, and Apache on a Windows® XP or 2003 machine. It's a piece of cake to install, has lots of nice little management goodies, and I recommend it wholeheartedly.
While this is the easiest package for Windows users, you need to add the following when configuring PHP on Linux: --memory-limit-enabled (in addition to any other options appropriate for your server). Under Windows, however, there is a workaround function provided.
There are many improvements that have taken place in PHP V5.2, and one critical area is that of memory management. The exact quote from README.ZEND_MM states: "The goal of the new memory manager (PHP5.2 and later) is reducing memory allocation overhead and speeding up memory management."
Here are some of the key items from the V5.2 release notes:
- Removed unnecessary
--disable-zend-memory-managerconfigure option - Added
--enable-malloc-mmconfigure option, which is enabled by default in debug builds to allow using internal and external memory debuggers - Allows tweaking the memory manager with
ZEND_MM_MEM_TYPEandZEND_MM_SEG_SIZEenvironment variables
To understand the implications of these new features, we need to delve into the fine art of memory management a bit and consider why allocation overhead and speed are a big deal.
One of the fastest-developing technologies in computing is memory and data storage, which are driven by the constant need for increases in speed and storage size. Early computers used cards as memory before moving to chip technology. Can you imagine working on a computer with only 1 KB of RAM? Many early computer programmers did. These pioneers realized very rapidly that to work within the restraints of the technology, they would have to be diligent to avoid overloading their systems with frivolous commands.
As PHP developers, we live in a much more convenient world to code in than our colleagues who code in C++ or other stricter languages. In our world, we do not have to concern ourselves with the handling of system memory because PHP handles that for us. In the rest of the programming world, however, responsible coders use various functions to ensure that executed commands do not overwrite some other program data -- thus, crippling that running program.
Memory management is usually handled by requests from the coder to allocate and release blocks of memory. Allocated blocks can hold data of any type, and this process blocks off a certain amount of memory for just that data and gives the program a method of addressing this data for when it needs to be accessed for operations. The program is expected to release allocated memory when it has completed any operations, and let the system and other programs use that memory. When a program does not release the memory back to the system, it is called a leak.
Leaks are a normal problem with any running program, and a certain amount is usually acceptable, especially when we know a running program will terminate soon and release all of any memory allocated to it by default.
With programs you run and terminate arbitrarily, like almost all client applications, this is the case. Server applications are expected to run indefinitely without termination or restart, making memory management absolutely vital to server daemon programming. Even a small leak would eventually grow into a system-debilitating problem on a long-running program as memory blocks are used and never released.
There are many potential uses for a persistent server daemon written in PHP, as with any language. But when we begin to use PHP for these purposes, we must also consider our memory usage.
Scripts that parse a great deal of data or which may be hiding an infinite loop have a tendency to consume large amounts of memory. Obviously, once the memory is exhausted, the performance of the server decreases, so we must also pay attention to how much memory we're using when we execute our scripts. While we can simply watch the amount of memory used by a script by turning the system monitor on, it will not tell us anything more useful than the status of the entire system memory. Sometimes we need to do a bit more than that to help us troubleshoot or optimize. Sometimes we just need more detail.
One way to get transparency into what our script is doing is to use an internal or external debugger. An internal debugger is one that appears to be the same process executing the script. Debuggers that are a separate process from the perspective of the OS are external. Memory analysis using a debugger is similar in either case, but the memory is accessed in different ways. Internally, a debugger has direct access to the same memory space as the running process, while an external debugger will access the memory via a socket.
There are many methods and available debugging servers (external) and libraries (internal) you can use to aid your development. In order to prepare your PHP installation for debugging, you can use the newly provided --enable-malloc-mm, which is enabled by default in a DEBUG build. This makes the environment variable USE_ZEND_ALLOC available to allow selection of malloc or emalloc memory allocations at runtime. Using malloc-type memory allocations will allow external debuggers to observe memory use while emalloc allocations will use the Zend memory manager abstraction, requiring internal debugging.
Memory management functions in PHP
In addition to making the memory manager more flexible and transparent, PHP V5.2 provides a new parameter for memory_get_usage() and memory_get_peak_usage(), which allow the viewing of the amount of used memory. The new Boolean mentioned in the notes is real_size. By invoking the function memory_get_usage($real); where $real = true, the result will be the real size of memory allocated from the system, including the memory-manager overhead, at the moment of invocation. Without the flag set, the data returned would be only the memory used within the running script, minus the memory-manager overhead.
memory_get_usage() and memory_get_peak_usage() differ in that the latter returns the peak memory usage so far for the running process that invokes it while the first only returns the usage at the moment of execution.
For memory_get_usage(), php.net provides the code snippet in Listing 1.
Listing 1. A
memory_get_usage() example
<?php
// This is only an example, the numbers below will
// differ depending on your system
echo memory_get_usage() . "\n"; // 36640
$a = str_repeat("Hello", 4242);
echo memory_get_usage() . "\n"; // 57960
unset($a);
echo memory_get_usage() . "\n"; // 36744
?>
|
In this simple example, we first echo the results of a straight up invocation of memory_get_usage(), which by the code annotation seems to have had a common result of 36640 bytes on the author's system. We then load up $a with 4,242 copies of "Hello" and run the function again. The output of this simple usage can be seen in Figure 1.
Figure 1. Example output of
memory_get_usage()
There is no example of memory_get_peak_usage() as the two are so similar. The syntax is identical. For the example code in Listing 1, there would be only one result, however, which is the peak memory usage at that moment. Let's take a look in Listing 2.
Listing 2. A
memory_get_peak_usage() example
<?php
// This is only an example, the numbers below will
// differ depending on your system
echo memory_get_peak_usage() . "\n"; // 36640
$a = str_repeat("Hello", 4242);
echo memory_get_peak_usage() . "\n"; // 57960
unset($a);
echo memory_get_peak_usage() . "\n"; // 36744
?>
|
The code in Listing 2 is identical to Figure 1, but memory_get_usage() has been swapped for memory_get_peak_usage(). Nothing much changes in output until we populate $a with the 4242 iterations of "Hello." Our memory jumps to 57960, representing our peak so far. When we check the memory usage peak, we get the highest value so far, so every further invocation will result in 57960 until we do something to use more memory than we did with $a (see Figure 2).
Figure 2. Example output of
memory_get_peak_usage()
One way to make sure we do not overtax the server we are hosting our application on is to limit the amount of memory used by any scripts executed by PHP. This isn't something we should have to do at all, but as PHP is a loosely typed language and is parsed at runtime, we sometimes get scripts that are poorly written unleashed upon our production applications. These scripts might execute a loop, or perhaps open a long list of files, forgetting to close the current file before opening a new one. Whatever the case, a poorly written script can end up chewing up a ton of memory before you know it.
In PHP.INI, you can use the configuration parameter memory_limit to specify the maximum amount of memory any script is able to run on the system. This is not a specific change to V5.2, but any discussion of the memory manager and its uses bears at least a quick look at this feature. It also leads me nicely to the last new features of the memory manager: environment variables.
Finally, what would programming be without being able to be a perfectionist and get it exactly right for your purposes? The new environment variables ZEND_MM_MEM_TYPE and ZEND_MM_SEG_SIZE allow you to do just that.
When the memory manager allocates large memory blocks, it does so in predetermined sizes, listed in the variable ZEND_MM_SEG_SIZE. The default size of these memory segments is 256 KB per block, but you can adjust these to suit your particular needs. For instance, if you were aware that the operations in one of your most common scripts was causing a large amount of wasted memory, you could adjust this size to more closely match the needs of the script, reducing the amount of memory allocated but remaining empty. In the right conditions, this kind of careful configuration tweaking can make a huge difference.
Retrieving memory usage on Windows
If you have a pre-built PHP Windows binary without the --enable-memory-limit option on when it was built, you need to go through this section before moving on. For Linux®, build PHP with the --enable-memory-limit option on when you configure your PHP build.
To retrieve memory usage using Windows binaries, create the following function.
Listing 3. Getting memory usage under Windows
<?php
function memory_get_usage(){
$output = array();
exec('tasklist /FI "PID eq '.getmypid().'" /FO LIST', $output );
return preg_replace( '/[^0-9]/', '', $output[5] ) * 1024;
}
?>
|
Save this in a file called function.php. Now you only have to include this file in scripts you wish to use it in.
Let's take a look at a practical example of using these settings to our advantage. There are times when you might wonder why memory isn't being properly deallocated at the end of a script. The reason is because some of the functions cause memory leaks themselves, especially if you're only using built-in PHP functions. Here, you will learn how to spot such problems. And to start off on your crusade for memory-leak finding, you'll create a test MySQL database, shown in Listing 4.
Listing 4. Creating the test database
mysql> create database memory_test;
mysql> use memory_test;
mysql> create table leak_test
( id int not null primary key auto_increment,
data varchar(255) not null default '');
mysql> insert into leak_test (data) values ("data1"),("data 2"),
("data 3"),("data 4"),("data 5"),("data6"),("data 7"),
("data 8"),("data 9"),("data 10");
|
This creates a simple table with an ID field and a data field.
In the next listing, imagine that our intrepid programmer is performing some MySQL functions, particularly applying a result to a variable using mysql_query(). When he does so, he notices that even though he calls mysql_free_result(), some of the memory doesn't get released, resulting in a growing memory usage for the Apache process (see Listing 5).
Listing 5. Example of memory-leak detection
for ( $x=0; $x<300; $x++ ) {
$db = mysql_connect("localhost", "root", "test");
mysql_select_db("test");
$sql = "SELECT data FROM test";
$result = mysql_query($sql); // The operation suspected of leaking
mysql_free_result($result);
mysql_close($db);
}
|
Listing 5 is a simple MySQL database operation that might be used anywhere. When we run our script, we notice some odd behavior relating to memory usage and need to check it out. To use the memory-management functions to allow us to verify where our error is happening, we use the code below.
Listing 6. Example of bracketing to find the culprit
<?php
if( !function_exists('memory_get_usage') ){
include('function.php');
}
echo "At the start we're using (in bytes): ",
memory_get_usage() , "\n<br>";
$db = mysql_connect("localhost", "user", "password");
mysql_select_db("memory_test");
echo "After connecting, we're using (in bytes): ",
memory_get_usage(),"\n<br>";
for ( $x=0; $x<10; $x++ ) {
$sql =
"SELECT data FROM leak_test WHERE id='".$x."'";
$result = mysql_query($sql); // The operation
// suspected of leaking.
echo "After query #$x, we're using (in bytes): ",
memory_get_usage(), "\n<br>";
mysql_free_result($result);
echo "After freeing result $x, we're using (in bytes): ",
memory_get_usage(), "\n<br>";
}
mysql_close($db);
echo "After closing the connection, we're using (in bytes): ",
memory_get_usage(), "\n<br>";
echo "Peak memory usage for the script (in bytes):".
memory_get_peak_usage();
?>
|
Note that at defined intervals, you're checking current memory usage. In the output below, you can see how the memory usage goes up with each call, providing a positive test for a memory leak by showing that our script keeps allocating memory to a function and not releasing it when it should.
Listing 7. Output of test script
At the start we're using (in bytes): 63216 After connecting, we're using (in bytes): 64436 After query #0, we're using (in bytes): 64760 After freeing result 0, we're using (in bytes): 64828 After query #1, we're using (in bytes): 65004 After freeing result 1, we're using (in bytes): 65080 After query #2, we're using (in bytes): 65160 After freeing result 2, we're using (in bytes): 65204 After query #3, we're using (in bytes): 65284 After freeing result 3, we're using (in bytes): 65328 After query #4, we're using (in bytes): 65408 After freeing result 4, we're using (in bytes): 65452 After query #5, we're using (in bytes): 65532 After freeing result 5, we're using (in bytes): 65576 After query #6, we're using (in bytes): 65656 After freeing result 6, we're using (in bytes): 65700 After query #7, we're using (in bytes): 65780 After freeing result 7, we're using (in bytes): 65824 After query #8, we're using (in bytes): 65904 After freeing result 8, we're using (in bytes): 65948 After query #9, we're using (in bytes): 66028 After freeing result 9, we're using (in bytes): 66072 After closing the connection, we're using (in bytes): 65108 Peak memory usage for the script (in bytes): 88748 |
What we have done is discovered some questionable activity on the execution of our script, then prepared our script to give us some understandable feedback. We ran the script again, using memory_get_usage() to look at the changes in memory usage, during each iteration. Based on the increasing value of our memory allocation, it's implied that we are creating a leak somewhere with out script. Because the memory is not freed by the mysql_free_result() function, we can assume that mysql_query() is allocating memory incorrectly.
The PHP V5.2 release includes some great new tools to help you gain better insight into your script's allocation of system memory and to take back control of the fine tuning of memory management overall. When used effectively, the new memory-management tools will support your debugging efforts to reclaim some of your system resources.
Learn
-
Read Part 2 and Part 3 of this series.
-
Read the PHP V5.2 release announcement.
-
"How to Manage Memory in PHP" is a good article on PHP memory management programming practices.
-
The Zend Developer Zone has documentation for memory manager functions.
-
Visit PHP.net for PHP documentation.
-
The article "A step-by-step how-to guide to install, configure, and test a Linux, Apache, Informix, and PHP server" contains a section on compiling the PHP interpreter for Linux.
-
See the bug report that inspired the practical example.
-
Learn how to migrate code developed in PHP V4 to V5 in "A PHP V5 migration guide.
-
For tutorials on learning to program with PHP, check out developerWorks' "Learning PHP" series.
-
Planet PHP is the PHP developer community news source.
-
PHP.net is the resource for PHP developers.
-
Check out the "Recommended PHP reading list."
-
Browse all the PHP content on developerWorks.
-
Expand your PHP skills by checking out IBM developerWorks' PHP project resources.
-
To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
-
Stay current with developerWorks' Technical events and webcasts.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
-
Visit Safari Books Online for a wealth of resources for open source technologies.
Get products and technologies
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
Discuss
-
Participate in developerWorks blogs and get involved in the developerWorks community.
-
Participate in the developerWorks PHP Developer Forum.




