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.
We can do simple memory management in PHP, without any complex tools.
- Use
memory_get_peak_usage()
to track peak memory usage. - 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, andgc_collect_cycles()
to activate the garbage collector. - Reduce the number of temporary variables and dependency on third-party libraries.
That’s all. No “special tools” nor “advanced techniques” are required, just some simple changes to the script – Read on for the examples!
TABLE OF CONTENTS
DOWNLOAD & NOTES
Here is the download link to the example code, so you don’t have to copy-paste everything.
EXAMPLE CODE DOWNLOAD
Just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.
SORRY FOR THE ADS...
But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.
Buy Me A Coffee Code Boxx eBooks
PHP MEMORY PERFORMANCE TRACKING
For this first section, let us walk through how to track memory usage and execution time – No special tools are required.
A) TRACK MEMORY USAGE
<?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
<?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 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.
PHP MEMORY EFFICIENT CODING
Now that we have eyes on memory usage and execution time, the next step is to improve the efficiency of the scripts. Here are a few simple tips to reduce memory usage.
1) READ LINE-BY-LINE FROM THE DATABASE
<?php
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8mb4");
define("DB_USER", "root");
define("DB_PASSWORD", "");
// (B) CONNECT TO DATABASE
$pdo = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET,
DB_USER, DB_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
// (C) PREPARE SQL
$stmt = $pdo->prepare("SELECT * FROM `TABLE`");
$stmt->execute();
// (D) BAD - FETCH ALL AT ONCE
// $results = $stmt->fetchAll();
// foreach ($results as $r) { print_r($r); }
// (E) PREFERRED - FETCH LINE-BY-LINE
while ($r = $stmt->fetch()) { print_r($r); }
// (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
<?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 exit("Failed to open " . $source);
while ($line = fgets($handle)) { 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
<?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
<?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 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 THE DATABASE
<?php
// (A) DATABASE SETTINGS
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8mb4");
define("DB_USER", "root");
define("DB_PASSWORD", "");
// (B) CONNECT TO DATABASE
$pdo = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET,
DB_USER, DB_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
// (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 `TABLE`");
$stmt->execute();
// (E) BAD - FETCH ALL AT ONCE
// $results = $stmt->fetchAll();
// foreach ($results as $r) { echo implode(",", $r) . "\r\n"; }
// (F) PREFERRED - FETCH & DIRECTLY OUTPUT
while ($r = $stmt->fetch()) { echo implode(",", $r) . "\r\n"; }
// (G) 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.
EXTRAS
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
.
memory_limit=256M
Or we can also set it in the script during runtime.
<?php
ini_set("memory_limit", "256M");
LINKS & REFERENCES
- Get memory usage – PHP
- Get peak memory usage – PHP
- Garbage collecting cycles – PHP
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!