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:
    Test nameDescriptionUbuntu 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_variableVariable Mention448048
    a.new_null_variableCreating a new null variable108208144
    a.unset_null_variableDelete variable-108-208-144
    stdClass.newObject Creation120232168
    stdClass.tovar1Creating an object and link $ a to it264512352
    stdClass.tovar2_unset_and_thesameDelete link $ a and recreate link $ a000
    stdClass.tovar3_unset_and_anotherDelete link $ a and create link $ b000
    stdClass.tovar4_anotherCreating an object and link $ c to it264512352
    stdClass.tovar5_addlinkLink $ a to the same object as $ b6412896
    stdClass.z.free_memoryRemoving $ a, $ b, and $ c links-592-1152-800
    myclass.a.emptyClass A Description70013441128
    myclass.aa.interfaceInterface Description A70013441128
    myclass.ab.finalDescription of the final class AB70013441128
    myclass.ac.abstractAC class abstract70013441128
    myclass.b.extended.emptyDescription of class B extending A70013441128
    myclass.c.empty.namespaceDescription of empty namespace C000
    myclass.d.constructClass D description with constructor110422881920
    myclass.dd.methodDD class description with method108822801912
    myclass.ddd.private.varPrivate variable DDD class description96018401472
    myclass.dddd.public.varDescription of DDDD class with public variable96018401472
    myclass.ddddd.static.varDescription of the DDDDD class with a static variable96018401472
    myclass.e.extended.destructDescription of class E with destructor extending class D134427042272
    myclass.e.instance.abCreating an AB Object and $ e Link to It264512352
    myclass.e.instance.dddddCreating a DDDDD Object and $ e Link to It000
    myclass.e.instance.eCreating an object E and link $ e to it000
    myclass.f.instance.dddddCreating a DDDDD Object and $ f Link to It264512352
    myclass.z.free_memoryRemoving links $ e, $ f-484-944-656
    zend.a.init.autoload Initializing autoload for ZendFramework127 444276,288249 232
    zend.a.init.model Initializing the default adapter for the base1 018 3882 081 6001 871 256
    zend.extended.controller1Controller Definition from Zend_Controller_Action. Along the way, loading standard Zend classes378,296809 384712 816
    zend.extended.controller2Controller Definition. Zend class is already loaded, see how much our class weighs11 32819 60816,008
    zend.extended.model1Model Definition by Zend_Db_Table. Along the way, loading of standard Zend classes takes place.27 93648 54440 224
    zend.extended.model2Definition of the model. Zend class is already loaded, see how much our class weighs27 93648 53640 208
    zend.use.model1.e.instance1 Creating a Model1 Object and $ e Link to It249246483432
    zend.use.model1.f.instance2 Creating a Model1 Object and Linking $ f to It176432562488
    zend.use.model1.g.instance3 Creating a Model1 Object and $ g Link to It176432562488
    zend.use.model2.e.instance1 Creating a Model2 Object and $ e Link to It7401400944
    zend.use.model2.f.instance2Creating a Model2 Object and $ f Linking to It000


    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:
    Test nameDescriptionUbuntu 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.emptyClass A Description84067214801256
    myclass.aa.interfaceInterface Description A85667615121264
    myclass.ab.finalDescription of the final class AB84467214881256
    myclass.ac.abstractAC class abstract85268015041264
    myclass.b.extended.emptyDescription of class B extending A91270015121264
    myclass.c.empty.namespaceDescription of empty namespace C176-16184-72
    myclass.d.constructClass D description with constructor125696024481736
    myclass.dd.methodDD class description with method126896824321728
    myclass.ddd.private.varPrivate variable DDD class description114096420001760
    myclass.dddd.public.varDescription of DDDD class with public variable113295220001760
    myclass.ddddd.static.varDescription of the DDDDD class with a static variable112495220001760
    myclass.e.extended.destructDescription of class E with destructor extending class D1528122828882160
    myclass.z.free_memoryRemoving links $ e, $ f-332-548-784-1024
    zend.a.init.autoload Initializing autoload for ZendFramework127 59616 196276,44028,992
    zend.a.init.model Initializing the default adapter for the base1 018 564251,8402 081 696479,280
    zend.extended.controller1Controller Definition from Zend_Controller_Action. Along the way, loading standard Zend classes378,46466 804809 608120 864
    zend.extended.controller2Controller Definition. Classes Zend are already loaded, we look how much our class weighs11,47611 14019 79219 056
    zend.extended.model1Model Definition by Zend_Db_Table. Along the way, loading of standard Zend classes takes place.28,08025,67648,70442 944
    zend.extended.model2Definition of the model. Zend class is already loaded, see how much our class weighs28,08025,70448,67242 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:
    Page typeUbuntu 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
    Post list5 328 6481 792 96810 938 1603 306 720
    Post and its comments5 372 3561 831 45211 015 3203 373 528
    Login form6 781 6562,277,16413 982 1044 187 600
    Registration form6 796 4962,291,56814 009 3844 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.

    Also popular now: