How much memory do objects consume in PHP and is it worth using the 64-bit version?
This post was inspired by the study of memory consumption for my current large ZendFramework project. As usual, according to the results of the study, I was shocked by our programmatic 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 will we measure
To begin with, we will determine how we will measure the "weight". Here is the template:
$startMemory = 0;
$startMemory = memory_get_usage();
// Измеряемое
echo (memory_get_usage() - $startMemory) . ' bytes' . PHP_EOL;
Such a template is suitable for measuring new allocated memory, that is, memory for variables. But it is impossible to measure how many definitions eat, that is, descriptions of functions and classes, since they are stored in memory before the script starts. In order to measure the definitions, we use the following template:
$startMemory = 0;
$startMemory = memory_get_usage();
// Измеряемое
include $testfile;
echo (memory_get_usage() - $startMemory - $include_overhead) . ' bytes' . PHP_EOL;
Where $ include_overhead is how much the include statement consumes to fit its internal needs. In this article, we will not study how we can measure $ include_overhead. I only note that the size of the consumed memory depends on 3 things:
- Absolute file path lengths
- How was this file 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 allocate memory for the future.
If anyone is interested in exploring this deeper, you can examine the file run.include-test.php , it very well illustrates the unevenness of memory consumption when include. I also note that in all the tests below we measure $ include_overhead approximately, because we need not exact values, but the tendency and differences between the 32-bit and 64-bit versions.
How much do the “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. Command to run in 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 launched from the console. Here are the results:PHP 5.3.10, ZF 1.11.11 | PHP 5.3.10, ZF 1.11.11 | PHP 5.3.15, ZF 1.11.4 | ||
---|---|---|---|---|
a.mention_variable | Variable Mention | 44 | 80 | 48 |
a.new_null_variable | Creating a new null variable | 108 | 208 | 144 |
a.unset_null_variable | Delete variable | -108 | -208 | -144 |
stdClass.new | Object Creation | 120 | 232 | 168 |
stdClass.tovar1 | Creating an object and link $ a to it | 264 | 512 | 352 |
stdClass.tovar2_unset_and_thesame | Delete link $ a and recreate link $ a | 0 | 0 | 0 |
stdClass.tovar3_unset_and_another | Delete link $ a and create link $ b | 0 | 0 | 0 |
stdClass.tovar4_another | Creating an object and link $ c to it | 264 | 512 | 352 |
stdClass.tovar5_addlink | Link $ a to the same object as $ b | 64 | 128 | 96 |
stdClass.z.free_memory | Removing $ a, $ b, and $ c links | -592 | -1152 | -800 |
myclass.a.empty | Class A Description | 700 | 1344 | 1128 |
myclass.aa.interface | Interface Description A | 700 | 1344 | 1128 |
myclass.ab.final | Description of the final class AB | 700 | 1344 | 1128 |
myclass.ac.abstract | AC class abstract | 700 | 1344 | 1128 |
myclass.b.extended.empty | Description of class B extending A | 700 | 1344 | 1128 |
myclass.c.empty.namespace | Description of empty namespace C | 0 | 0 | 0 |
myclass.d.construct | Class D description with constructor | 1104 | 2288 | 1920 |
myclass.dd.method | DD class description with method | 1088 | 2280 | 1912 |
myclass.ddd.private.var | Private variable DDD class description | 960 | 1840 | 1472 |
myclass.dddd.public.var | Description of DDDD class with public variable | 960 | 1840 | 1472 |
myclass.ddddd.static.var | Description of the DDDDD class with a static variable | 960 | 1840 | 1472 |
myclass.e.extended.destruct | Description of class E with destructor extending class D | 1344 | 2704 | 2272 |
myclass.e.instance.ab | Creating an AB Object and $ e Link to It | 264 | 512 | 352 |
myclass.e.instance.ddddd | Creating a DDDDD Object and $ e Link to It | 0 | 0 | 0 |
myclass.e.instance.e | Creating an object E and link $ e to it | 0 | 0 | 0 |
myclass.f.instance.ddddd | Creating a DDDDD Object and $ f Link to It | 264 | 512 | 352 |
myclass.z.free_memory | Removing links $ e, $ f | -484 | -944 | -656 |
zend.a.init.autoload | Initializing autoload 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 | Controller Definition from Zend_Controller_Action. Along the way, loading standard Zend classes | 378,296 | 809 384 | 712 816 |
zend.extended.controller2 | Controller Definition. Zend class is already loaded, see how much our class weighs | 11 328 | 19 608 | 16,008 |
zend.extended.model1 | Model Definition by Zend_Db_Table. Along the way, loading of standard Zend classes takes place. | 27 936 | 48 544 | 40 224 |
zend.extended.model2 | Definition of the model. Zend class is 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 Link to It | 2492 | 4648 | 3432 |
zend.use.model1.f.instance2 | Creating a Model1 Object and Linking $ f to It | 1764 | 3256 | 2488 |
zend.use.model1.g.instance3 | Creating a Model1 Object and $ g Link to It | 1764 | 3256 | 2488 |
zend.use.model2.e.instance1 | Creating a Model2 Object and $ e Link to It | 740 | 1400 | 944 |
zend.use.model2.f.instance2 | Creating a Model2 Object and $ f Linking to It | 0 | 0 | 0 |
You may notice that the Gentoo build consumes 10-20% less memory, and in rare cases, the savings come up to 50%. Apparently, the size of internal structures depends on optimizations for the processor. For the experiment, I rebuilt php with different variants of CFLAGS, but it did not start to consume more from this. Apparently, the difference is not manifested due to the rebuilding of PHP itself, but from the rebuilding of standard C # libraries.
As noted above, it’s difficult to accurately measure $ include_overhead, so if you run these tests, you may get memory consumption that jumps to 4, 8, 12, 16 bytes, even in tests that should consume the same. Do not focus on this. I ran the tests in a different order and more or less set the true memory consumption.
Let's talk about tests related to ZendFramework. Loading Zend's class definitions into memory eats up significant resources, while object references already consume not so much. Controller2 is needed to check how much a similar controller will eat if all the intermediate classes are already in memory. Model2 is created for the same purpose.
Potentially, using the 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 affects:
PHP 5.3.10, ZF 1.11.11, Empty cache | PHP 5.3.10, ZF 1.11.11, Refresh | PHP 5.3.10, ZF 1.11.11, Empty cache | 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 | Description of the final class AB | 844 | 672 | 1488 | 1256 |
myclass.ac.abstract | AC class abstract | 852 | 680 | 1504 | 1264 |
myclass.b.extended.empty | Description of class B extending A | 912 | 700 | 1512 | 1264 |
myclass.c.empty.namespace | Description of empty namespace C | 176 | -16 | 184 | -72 |
myclass.d.construct | Class D description with constructor | 1256 | 960 | 2448 | 1736 |
myclass.dd.method | DD class description with method | 1268 | 968 | 2432 | 1728 |
myclass.ddd.private.var | Private variable DDD class description | 1140 | 964 | 2000 | 1760 |
myclass.dddd.public.var | Description of DDDD class with public variable | 1132 | 952 | 2000 | 1760 |
myclass.ddddd.static.var | Description of the DDDDD class with a static variable | 1124 | 952 | 2000 | 1760 |
myclass.e.extended.destruct | Description of class E with destructor extending class D | 1528 | 1228 | 2888 | 2160 |
myclass.z.free_memory | Removing links $ e, $ f | -332 | -548 | -784 | -1024 |
zend.a.init.autoload | Initializing autoload 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 | Controller Definition from Zend_Controller_Action. Along the way, loading standard Zend classes | 378,464 | 66 804 | 809 608 | 120 864 |
zend.extended.controller2 | Controller Definition. Classes Zend are already loaded, we look how much our class weighs | 11,476 | 11 140 | 19 792 | 19 056 |
zend.extended.model1 | Model Definition by Zend_Db_Table. Along the way, loading of standard Zend classes takes place. | 28,080 | 25,676 | 48,704 | 42 944 |
zend.extended.model2 | Definition of the model. Zend class is 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. First: xcache loses (almost always) by 10-15% in memory savings. And secondly: xcache immediately returns files from the cache, while APC - only after repeated access. Though useless, but an advantage.
I’ll note right away 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, although it saves us memory for definitions, is not completely, since PHP, apparently, transfers some pieces from the cache to the current session.
Now let's move from abstract tests to real ones.
Testing a small application on ZendFramework
For testing, we took the test task of one of our programmers ( Simple-blog ): 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 consumed during page generation. Results:PHP 5.3.10, ZF 1.11.11, Empty cache | PHP 5.3.10, ZF 1.11.11, Refresh | PHP 5.3.10, ZF 1.11.11, Empty cache | PHP 5.3.10, ZF 1.11.11, Refresh | |
---|---|---|---|---|
Post list | 5 328 648 | 1 792 968 | 10 938 160 | 3 306 720 |
Post and its 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 assembly for Gentoo was tested; it turned out to be 25% more effective in all tests.
conclusions
- 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. The gain will be ~ 1.8 times.
- In OSs where packages are sharpened for the current architecture, 25% additional memory can be saved.
- Nothing consumes memory in PHP like a heavy framework. Using the 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 for yourself a balance of popularity / performance.
- The situation, which is shown in the picture to attract attention, can be obtained if the size of the APC cache is exhausted. This is not difficult to achieve if you have many sites on the same machine, and you installed APC without checking if you have enough memory. At the same time, statistics (apc.php) will tell you that you still have about 40% of the memory, but you shouldn’t really believe it, because APC has a bad memory manager and it just does not know how to use it effectively. Better always pay attention to hits and miss values.
Kodjara
UPD
AntonShevchuk added test results for PHP 5.4 . PHP 5.4 looks much more economical compared to 5.3. The official documentation also confirms this.