
This post was inspired by a study of memory consumption for my current big project at ZendFramework. As usual, according to the research, I was shocked by our programmer arrogance, which is often present when we write something big in PHP. And, probably, not only in PHP.
But first things first.
')
This article is a logical continuation of the following articles:
How are we going to measure
To begin with, we will define how we will measure “weight”. Here is the template:
$startMemory = 0; $startMemory = memory_get_usage();
This pattern is suitable for measuring the new allocated memory, that is, memory for variables. But to measure how many definitions eat, that is, descriptions of functions and classes, such an approach is impossible, since they are stored in memory before the script starts. To measure definitions, use the following template:
$startMemory = 0; $startMemory = memory_get_usage();
Where $ include_overhead is how much is the include statement to fit its internal needs. In this article we will not study how we can measure $ include_overhead. I will note only that the size of the devoured memory depends on 3 things:
- Absolute path length to file
- How this file was included (every 8, 16, 32, 64, etc. there is an additional selection for internal structures)
- The fullness of the internal structures of PHP itself, which periodically replenish their memories for the future.
If anyone is interested in understanding this more deeply, then you can examine the file
run.include-test.php , it very well illustrates the unevenness of devouring memory with include. Also note that in all tests below we measure $ include_overhead approximately, because we need not exact values, but a trend and differences between the 32-bit and 64-bit versions.
How much do "objects" weigh
So TestSuite was written to automatically run a large number of tests. All tests were run in VirtualBox for
Ubuntu 12.04.1 LTS i386 and
Ubuntu 12.04.1 LTS amd64 . PHP version - 5.3.10, ZendFramework - 1.11.11. The command to run in the console:
php run.testsuite-without-accelerator.php
Additionally, I did a test on my machine with Gentoo amd64 for control. PHP accelerators do not work when started from the console. Here are the results:
Test name | Description | Ubuntu x86 PHP 5.3.10, ZF 1.11.11 | Ubuntu x86-64 PHP 5.3.10, ZF 1.11.11 | Gentoo x86-64 PHP 5.3.15, ZF 1.11.4 |
---|
a.mention_variable | Variable reference | 44 | 80 | 48 |
a.new_null_variable | Creating a new variable with a null value | 108 | 208 | 144 |
a.unset_null_variable | Deleting variable | -108 | -208 | -144 |
stdClass.new | Object creation | 120 | 232 | 168 |
stdClass.tovar1 | Creating an object and linking $ a to it | 264 | 512 | 352 |
stdClass.tovar2_unset_and_thesame | Removing link $ a and recreating link $ a | 0 | 0 | 0 |
stdClass.tovar3_unset_and_another | Remove link $ a and create link $ b | 0 | 0 | 0 |
stdClass.tovar4_another | Creating an object and linking $ c to it | 264 | 512 | 352 |
stdClass.tovar5_addlink | Creating the link $ a to the same object as $ b | 64 | 128 | 96 |
stdClass.z.free_memory | Removing links $ a, $ b and $ c | -592 | -1152 | -800 |
myclass.a.empty | Class A Description | 700 | 1344 | 1128 |
myclass.aa.interface | Interface Description A | 700 | 1344 | 1128 |
myclass.ab.final | AB Final Class Description | 700 | 1344 | 1128 |
myclass.ac.abstract | Description of the abstract class AC | 700 | 1344 | 1128 |
myclass.b.extended.empty | Description of Class B Extending A | 700 | 1344 | 1128 |
myclass.c.empty.namespace | Description of blank namespace C | 0 | 0 | 0 |
myclass.d.construct | Description of class D with constructor | 1104 | 2288 | 1920 |
myclass.dd.method | DD class description with method | 1088 | 2280 | 1912 |
myclass.ddd.private.var | DDD class description with private variable | 960 | 1840 | 1472 |
myclass.dddd.public.var | DDDD class description with public variable | 960 | 1840 | 1472 |
myclass.ddddd.static.var | DDDDD class description with static variable | 960 | 1840 | 1472 |
myclass.e.extended.destruct | Class E Description with a Destructor Extending Class D | 1344 | 2704 | 2272 |
myclass.e.instance.ab | Creating an AB object and $ e linking to it | 264 | 512 | 352 |
myclass.e.instance.ddddd | Creating the DDDDD object and the $ e link to it | 0 | 0 | 0 |
myclass.e.instance.e | Creating an object E and linking $ e to it | 0 | 0 | 0 |
myclass.f.instance.ddddd | Creating a DDDDD object and linking $ f to it | 264 | 512 | 352 |
myclass.z.free_memory | Removing links $ e, $ f | -484 | -944 | -656 |
zend.a.init.autoload | Autoload initialization for ZendFramework | 127 444 | 276 288 | 249,232 |
zend.a.init.model | Initializing the default adapter for the base | 1,018,388 | 2,081,600 | 1 871 256 |
zend.extended.controller1 | Definition of the controller from Zend_Controller_Action. Along the way, standard Zend classes are loaded. | 378,296 | 809 384 | 712,816 |
zend.extended.controller2 | Definition of the controller. The Zend classes are already loaded, see how much our class weighs. | 11 328 | 19,608 | 16 008 |
zend.extended.model1 | Model definition from Zend_Db_Table. Along the way, standard Zend classes are loaded. | 27,936 | 48 544 | 40,224 |
zend.extended.model2 | Model definition. The Zend classes are already loaded, see how much our class weighs. | 27,936 | 48 536 | 40 208 |
zend.use.model1.e.instance1 | Creating a Model1 object and $ e references to it | 2492 | 4648 | 3432 |
zend.use.model1.f.instance2 | Creating a Model1 object and $ f references to it | 1764 | 3256 | 2488 |
zend.use.model1.g.instance3 | Creating a Model1 object and linking $ g to it | 1764 | 3256 | 2488 |
zend.use.model2.e.instance1 | Creating a Model2 object and $ e references to it | 740 | 1400 | 944 |
zend.use.model2.f.instance2 | Creating a Model2 object and $ f references to it | 0 | 0 | 0 |

You may notice that the assembly of Gentoo consumes 10-20% less memory, and in rare cases, the savings reach 50%. Apparently, the size of the internal structures depends on the optimizations for the processor. For the experiment, I rebuilt php with different variants of CFLAGS, but he didn’t consume more of it. Apparently the difference is not due to rebuilding PHP itself, but from rebuilding standard Sish libraries.

As noted above, it is difficult to accurately measure $ include_overhead, so if you run these tests, then you can make it so that memory consumption jumps to 4, 8, 12, 16 bytes, even in tests that should consume the same. Do not focus on this attention. I ran the tests in a different order and more or less found true memory consumption.
Let's talk about tests related to ZendFramework. Loading class definitions of Zend into memory erases significant resources, whereas object references already consume not so much. Controller2 is needed to check how much a similar controller will eat, if all intermediate classes are already in memory. Model2 is designed for the same purpose.
Potentially, the use of a PHP accelerator will save us memory on all definitions, because they will already be stored in memory. Let's check this statement.
Accelerator Testing
APC was taken for testing, and tests were run via the web using a script:
php run.testsuite-with-accelerator.php
The results are shown only tests, where the accelerator has an effect:
Test name | Description | Ubuntu x86 PHP 5.3.10, ZF 1.11.11, Empty cache | Ubuntu x86 PHP 5.3.10, ZF 1.11.11, Refresh | Ubuntu x86-64 PHP 5.3.10, ZF 1.11.11, Empty cache | Ubuntu x86-64 PHP 5.3.10, ZF 1.11.11, Refresh |
---|
myclass.a.empty | Class A Description | 840 | 672 | 1480 | 1256 |
myclass.aa.interface | Interface Description A | 856 | 676 | 1512 | 1264 |
myclass.ab.final | AB Final Class Description | 844 | 672 | 1488 | 1256 |
myclass.ac.abstract | Description of the abstract class AC | 852 | 680 | 1504 | 1264 |
myclass.b.extended.empty | Description of Class B Extending A | 912 | 700 | 1512 | 1264 |
myclass.c.empty.namespace | Description of blank namespace C | 176 | -sixteen | 184 | -72 |
myclass.d.construct | Description of class D with constructor | 1256 | 960 | 2448 | 1736 |
myclass.dd.method | DD class description with method | 1268 | 968 | 2432 | 1728 |
myclass.ddd.private.var | DDD class description with private variable | 1140 | 964 | 2000 | 1760 |
myclass.dddd.public.var | DDDD class description with public variable | 1132 | 952 | 2000 | 1760 |
myclass.ddddd.static.var | DDDDD class description with static variable | 1124 | 952 | 2000 | 1760 |
myclass.e.extended.destruct | Class E Description with a Destructor Extending Class D | 1528 | 1228 | 2888 | 2160 |
myclass.z.free_memory | Removing links $ e, $ f | -332 | -548 | -784 | -1024 |
zend.a.init.autoload | Autoload initialization for ZendFramework | 127 596 | 16 196 | 276,440 | 28,992 |
zend.a.init.model | Initializing the default adapter for the base | 1,018,564 | 251,840 | 2,081,696 | 479 280 |
zend.extended.controller1 | Definition of the controller from Zend_Controller_Action. Along the way, standard Zend classes are loaded. | 378 464 | 66,804 | 809 608 | 120 864 |
zend.extended.controller2 | Definition of the controller. The Zend classes are already loaded, see how much our class weighs | 11,476 | 11,140 | 19,792 | 19,056 |
zend.extended.model1 | Model definition from Zend_Db_Table. Along the way, standard Zend classes are loaded. | 28,080 | 25,676 | 48,704 | 42 944 |
zend.extended.model2 | Model definition. The Zend classes are already loaded, see how much our class weighs. | 28,080 | 25,704 | 48 672 | 42 960 |

I also did some tests with xcache and noticed 2 differences from APC. Firstly: xcache loses (almost always) by 10-15% in terms of memory savings. And secondly: xcache immediately gives the files from the cache, while the APC - only after repeated treatment. Although useless, but an advantage.

Immediately, I note that there is much more scatter in the results than when testing without an accelerator, since the files were not renamed and $ include_overhead was calculated with a big error.
As we can see, the accelerator saves us memory for definitions, but not completely, since PHP apparently transfers some pieces from the cache to the current session.
Now we will pass from abstract tests to quite real.
Testing a small application on ZendFramework
For testing, a test task of one of our programmers (
Simple-blog ) was taken: a collective blog service with functions: registration, authorization, reading the list of posts, opening a post and commenting on it. At the end of index.php it was written:
echo memory_get_peak_usage();
to check how much memory the script devoured during page generation. Results:
Page type | Ubuntu x86 PHP 5.3.10, ZF 1.11.11, Empty cache | Ubuntu x86 PHP 5.3.10, ZF 1.11.11, Refresh | Ubuntu x86-64 PHP 5.3.10, ZF 1.11.11, Empty cache | Ubuntu x86-64 PHP 5.3.10, ZF 1.11.11, Refresh |
---|
List of posts | 5 328 648 | 1 792 968 | 10,938,160 | 3,306,720 |
Post and his comments | 5,372,356 | 1,831,452 | 11,015,320 | 3,373,528 |
Login form | 6,781,656 | 2,277,164 | 13 982 104 | 4,187,600 |
Registration form | 6,796,496 | 2,291,568 | 14 009 384 | 4,211,432 |
Additionally, the Gentoo build was tested, it was 25% more efficient in all tests.
findings
- If memory is an expensive resource (for example, VPS) and 64-bit numbers are not really needed, then it makes sense to use a 32-bit version of the OS. Winning will be ~ 1.8 times.
- In the OS, in which there is a sharpening of packages for the current architecture, you can further save 25% of memory.
- Nothing consumes PHP memory more than a heavy framework. The use of an accelerator does not save you from eating memory with heavy frameworks. Perhaps it makes sense to familiarize yourself with the following comparison of PHP frameworks in order to choose the balance of popularity / performance.
- The situation, which is depicted in the picture to attract attention, can be obtained if the size of the APC cache will be exhausted. This is not difficult to achieve, if you have many sites on the same machine, and you have installed APC, without checking whether you have enough memory. At the same time, statistics (apc.php) will tell you that you still have about 40% of the memory, but she shouldn’t particularly believe it, because APC has a bad memory manager and he just doesn’t know how to use it efficiently. Better always pay attention to hits and miss values.
Coder
UPD
AntonShevchuk added
results for tests for PHP 5.4 . PHP 5.4 looks much more economical compared to 5.3.
Official documentation also confirms this.