ALEMIL

Welcome to a blog about game & application development

7 things to know, when PHP is too slow

7 things to know, when PHP is too slow 2018-02-26 23:33:31

PHP is not known to be a demon of speed and it can sometimes happen that programmers do not account for how successful a web application can quickly become.

Things that work "fine" when 300 users are topping the groups in a database can make the whole application literally unresponsive when there is a need to import 3000 users into a single group.

It is not even that big of a number in terms of user count, but it is a big number when you consider that there can be 3000 select queries in your application that can themselves make more queries for related data.

Below are 7 things that can help you with performance problems of your PHP applications.

1. Load data from file one line at a time


For experienced developers, this one is probably a no-brainer. Loading files puts them in memory with additional overhead and if the file is big enough your program will crash with out of memory error message.
$file = file_get_contents($filename);
PHP Fatal error:  Allowed memory size of 536870912 bytes exhausted (tried to allocate 266240 bytes)
By looping and parsing file partially, line by line, gathering all the necessary data before preserving it in the database, you will use much less memory at once.
$file = new SplFileObject($filename); // SplFileObject is a PHP internal class to deal with files.
// Loop line by line processing data from the file
while (!$file->eof()) {
// $line will be overriden on each loop iteration
$line = $file->fgets();
// Process the line further, if you need more than one line you can add it to array before you process it.
}

2. When querying SQL, never loop SELECT statements


PDO (PHP Data Objects) is a bridge between PHP and SQL (or NoSQL) database. This is often the biggest performance bottleneck in PHP applications.

Even with poorly designed databases, you can gain a lot of speed by moving your logic to plain SQL. If you reduce the number of queries to a minimum, you can gain a significant boost in performance!

The most common error I have seen is pulling one record at a time in a loop. It can often be easily resolved by using WHERE IN statement. If more than one field is used when searching, they can be matched with OR WHERE condition. For simplicity, I will use a DB class that abstracts connection to the database for me:
// This loop will make a select query for every user. 
// $users is an array of integer identifiers for simplicity.
foreach ($users as $userId) {
// Always use prepared statements for speed and security!
$result = DB::raw('SELECT * FROM articles WHERE user_id = ?', $userId);
// Process record further
}

// Below example will query database once before the loop and just process the records pulled.
$placeholders = array_fill(0, count($users), '?');
$results = DB::raw("SELECT * FROM articles WHERE user_id IN $placeholders", $users);
foreach ($results as $result) {
// Process record further
}

3. Use pagination


When your application grows, you might have over a million records in your database table. Pulling them all at once would take quite a bit of memory, that you might not have.

Use server-side pagination to pull and process just part of the data. If you need to take advantage of everything you have for some calculations, try to move aggregations to SQL. You can paginate data pulled from SQL by using LIMIT and SKIP.
// Pull the first page
SELECT * FROM users ORDER BY name ASC LIMIT 100 SKIP 0
// Pull the second page when browser requests it
SELECT * FROM users ORDER BY name ASC LIMIT 100 SKIP 100

4. Use foreach instead of for loops


This one is not as big as the three before it, but it is often a good practice to use a foreach loop instead of for when it is applicable. It is a tiny bit quicker and arguably a lot more readable.
// Count will be processed every loop iteration
for ($i = 0; $i < count($array); $i++) {
echo $array[$i];
}
foreach ($array as $key => $record) {
echo $record;
}

5. Measure your function execution time


To quickly measure how much time a part of your code takes, you can use the microtime function.
$start = microtime(true);
for ($i = 0; $i < 10000000; $i++) {
$n = ;
}
echo (microtime(true) - $start) . ' seconds';
0.10738110542297 seconds

6. Check memory usage of your script


If you don't know what is causing out of memory error, you can measure your memory usage with below commands
memory_get_usage() - current usage
memory_get_peak_usage() - the most memory that was used during execution of your script.

$i = 0;
// We measure current amount of RAM used by this PHP process
echo memory_get_usage();
echo PHP_EOL; // New line for convenience
// We add more stuff
$s = [1, 2, 3, 4, 5];
// We check memory usage again and compare for difference
echo memory_get_usage();
10864408
10864440

From that example, we know that our array of 5 integers take 32 bytes of memory. Results may differ depending on your environment and PHP version.

7. When in doubt, profile your code


Arguably the best method of optimization is by profiling your code. You can very quickly find performance bottlenecks if you use a proper tool that will measure every part of your script.

Very popular tool for this purpose is Xdebug. It might be a bit of a drag to set it up, but it is well worth it. It will save you a lot of time in the long run.

You have to explicitly enable profiling in xdebug configuration file, as this adds a bit of overhead to the execution. The file returned will have a .cachegrind extension and you will need to find a program for your OS to read it.



As a reference:
Incl. - The full time that was spent in a function and all children functions.
Self - Time spent in function instructions but not children functions.
Called - How many times a function was called during script execution.
Function - Name of the function

There are many more ways to optimize PHP scripts. Hopefully, you now have the tools to measure how your changes affect execution speed or memory usage and will be able to improve the code when a need arise.


Comments +