Simple Memory Management Tips & Tracking In PHP

Welcome to a quick tutorial on memory management in PHP. Running out of memory from massive scripts? While the easy way is to install more memory into the server, I am pretty sure that is not the most efficient way.

Simple memory tracking and management in PHP involves a few easy steps.

  1. Use memory_get_peak_usage() to track the peak memory usage.
  2. Apply some changes to improve the script efficiency.
    • Do not read entire files at once, read line-by-line.
    • Do not fetch all entries from the database at once, fetch one-by-one.
    • Use unset($VAR) to manually remove unused variables.
    • Use gc_collect_cycles() to manually activate the garbage collector.
    • Reduce the number of temporary variables and dependency on third-party libraries.
  3. Test the script, monitor memory usage, and review as required.

That’s all. No “special tools” nor “advanced techniques” are required, just some simple changes to the script – Read on for the examples!

ⓘ I have included a zip file with all the example source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

Download & Notes Performance Tracking Performance Coding
Useful Bits & Links The End

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

EXAMPLE CODE DOWNLOAD

Click here to download the source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

QUICK NOTES

If you spot a bug, please feel free to comment below. I try to answer questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

 

PERFORMANCE TRACKING

For this first section, let us walk through how to track the memory usage and execution time – No special tools required.

 

A) TRACK MEMORY USAGE

A-track-mem.php
<?php
// (A) GENERATE A LARGE ARRAY
$arr = [];
for ($i=0; $i<99999; $i++) { $arr[] = $i; }
 
// (B) MEMORY USAGE WITH ARRAY IN MEMORY
echo "<strong>WITH LARGE ARRAY IN MEMORY</strong><br>";
echo "Current usage: " . memory_get_usage() . " bytes<br>";
echo "Peak usage: " . memory_get_peak_usage() . " bytes<br><br>";
 
// (C) MEMORY USAGE AFTER ARRAY IS REMOVED
unset($arr);
echo "<strong>AFTER ARRAY IS REMOVED</strong><br>";
echo "Current usage: " . memory_get_usage() . " bytes<br>";
echo "Peak usage: " . memory_get_peak_usage() . " bytes";

On my computer, the output is:

WITH LARGE ARRAY IN MEMORY
Current usage: 6683280 bytes
Peak usage: 6683360 bytes
 
AFTER ARRAY IS REMOVED
Current usage: 391744 bytes
Peak usage: 6683360 bytes

As you can see, PHP provides 2 different functions to track memory usage, and they are totally different.

  • memory_get_usage() returns the current memory usage, at the specified line of the script.
  • memory_get_peak_usage() returns the highest recorded memory usage, at the specified line of the script.

For the purpose of memory tracking, memory_get_peak_usage() will probably make more sense.

 

B) TIME TAKEN TO EXECUTE

B-track-time.php
<?php
// (A) START TIMING
$time = microtime(true);
 
// (B) GENERATE A LARGE ARRAY
$arr = [];
for ($i=0; $i<99999; $i++) { $arr[] = $i; }
 
// (C) TOTAL TIME TAKEN
$time = microtime(true) - $time;
echo "Time taken - $time secs";

Yes, this is not tracking the memory usage but is an important metric nonetheless. We do not want a massive script to hog a huge chunk of memory for extended periods of time.

 

 

TRACKING WITH TOOLS

No “special tools” are required for performance tracking. But if you prefer “something advanced”, check out Xdebug.

 

PERFORMANCE CODING

Now that we have eyes on the memory usage and execution time, the next step is to improve the performance of the script. Here are a few simple tips to reduce memory usage.

 

1) READ LINE-BY-LINE FROM THE DATABASE 

1-db-line-by-line.php
<?php
// (A) DATABASE SETTINGS
define('DB_HOST', 'localhost');
define('DB_NAME', 'test');
define('DB_CHARSET', 'utf8');
define('DB_USER', 'root');
define('DB_PASSWORD', '');
 
// (B) CONNECT TO DATABASE
$pdo = new PDO(
  "mysql:host=" . DB_HOST . ";charset=" . DB_CHARSET .";dbname=" . DB_NAME,
  DB_USER, DB_PASSWORD, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false
  ]
) or die("Failed to connect to the database");
 
// (C) PREPARE SQL
$stmt = $pdo->prepare("SELECT * FROM `USERS`");
$stmt->execute();
 
// (D) BAD - FETCH ALL AT ONCE
// $results = $stmt->fetchAll();
// foreach ($results as $r) { echo "<div>".$r['name']."</div>"; }
 
// (E) PREFERRED - FETCH LINE-BY-LINE
while ($r = $stmt->fetch(PDO::FETCH_NAMED)) {
  echo "<div>".$r['name']."</div>";
}
 
// (F) CLOSE DATABASE CONNECTION
if ($stmt !== null) { $stmt = null; }
if ($pdo !== null) { $pdo = null; }
 
// (G) PEAK MEMORY USED
echo "PEAK USAGE - " . memory_get_peak_usage();

Need to output a huge list of records into HTML or to a document? Don’t fetch them all at once. Fetch and deal with the records one-by-one instead.

 

2) READ FILE LINE-BY-LINE

2-file-line-by-line.php
<?php
// (A) TARGET FILE
$source = "FILE.txt";

// (B) BAD - READ EVERYTHING INTO A STRING
// $contents = file_get_contents($source);
// print_r($contents);

// (C) PREFERRED - READ LINE-BY-LINE
$handle = fopen($source, "r") or die("Failed to open " . $source);
while (($line = fgets($handle)) !== false) { echo $line; }
fclose($handle);

// (D) PEAK MEMORY USED
echo "PEAK USAGE - " . memory_get_peak_usage();

Same old story, don’t read a massive file into a single string at once. Read line-by-line instead.

 

 

3) REMOVE USELESS VARIABLES

3-reduce-var.php
<?php
// (A) BAD - INTRODUCE NEEDLESS VARIABLES
$varA = 123;
$varB = 234;
$varC = $varA + $varB;

// (B) IF $VARA $VARB ARE NOT USED ANYWHERE ELSE - DON'T INTRODUCE THEM
$varC = 123 + 234;

// (C) PEAK MEMORY USED
echo "PEAK USAGE - " . memory_get_peak_usage() . "<br>";

Some of you code ninjas may laugh and brush this aside as “trivial”. How much memory can a single variable take? Well, the more useless variables you have floating around, the more memory the script consumes. Remove hundreds of such useless flags and variables in a huge project, and you will see a huge difference.

 

4) MANUALLY REMOVE TEMPORARY VARIABLES

4-manual-unset.php
<?php
// (A) TEMPORARY VARIABLES FOR CALCULATIONS
$varA = 123;
$varB = 234;
$varC = $varA + $varB;

// (B) MANUALLY REMOVED
unset($varA);
unset($varB);
gc_collect_cycles();

Temporary flags, variables, arrays, and objects – We can manually remove them to reduce the resource hogging. Garbage collection is quite smart in PHP, but we can also call gc_collect_cycles() to clear things up.

 

 

5) DIRECT OUTPUT FROM DATABASE

5-direct-output.php
<?php
// (A) DATABASE SETTINGS
define('DB_HOST', 'localhost');
define('DB_NAME', 'test');
define('DB_CHARSET', 'utf8');
define('DB_USER', 'root');
define('DB_PASSWORD', '');

// (B) CONNECT TO DATABASE
$pdo = new PDO(
  "mysql:host=" . DB_HOST . ";charset=" . DB_CHARSET .";dbname=" . DB_NAME,
  DB_USER, DB_PASSWORD, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false
  ]
) or die("Failed to connect to the database");

// (C) HTTP HEADERS
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary"); 
header("Content-disposition: attachment; filename=\"export.csv\"");

// (D) PREPARE SQL
$stmt = $pdo->prepare("SELECT * FROM `test`");
$stmt->execute();

// (E) FETCH & DIRECTLY OUTPUT
while ($row = $stmt->fetch(PDO::FETCH_NAMED)) {
  echo implode(",", $row) . "\r\n";
}

// (F) CLOSE DATABASE CONNECTION
if ($stmt !== null) { $stmt = null; }
if ($pdo !== null) { $pdo = null; }

Remember the “read line-by-line” example earlier? This is kind of a follow-up. If you are creating a CSV file or outputting to HTML – Just directly output the records. Don’t need to go the roundabout way of fetching the all rows into a massive array first, then use a for loop to run through them.

 

 

USEFUL BITS & LINKS

That’s all for this guide, and here is a small section on some links that may be useful to you.

 

SETTING THE MEMORY LIMIT

If all else fails, the only way is to raise the memory limits in php.ini.

php.ini
memory_limit=256M

Or we can also set it in the script during runtime.

<?php
ini_set('memory_limit', '256M');

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end of this guide. I hope that it has helped you with your project, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

Leave a Comment

Your email address will not be published. Required fields are marked *