Memory DataObject vs Array in PHP

Today I wanted to create a tuple object, so I have a clear type definition. The last knowledge I had was that data-objects have a smaller memory footprint than arrays in PHP.

This was true for a very long time, also the immutability since every array is always a left copy unless you explicitly add the & to the passing parameter.

I wrote a little benchmark and the results were quite interesting to me. As a result I will remain with arrays for tuple return structures.

Since their memory footprint is way below a possible tuple object. Just out of curiosity i was testing the SPLFixedArray which also was known to have a smaller footprint than a normal array.

It seams like this has changed as well.

This has been tested with PHP 7.2 on a linux machine. I post the code so you can verify this on your machines, I think it's likely to remain true.

<?php

declare(strict_types=1);

class Data {

    static $template;

    public $v;
    public $a;
    public $b;

    public static function create($a, $b, $c) {
        if (!self::$template) {
            self::$template = new self();
        }

        $retVal = clone self::$template;
        $retVal->a = $a;
        $retVal->b = $b;
        $retVal->v = $c;


        return $retVal;
    }
}

echo "before loop \n";
echo memory_get_usage(true) / 1024 / 1024;
echo "\n";

$objectStorage = [];
for ($i = 0; $i < 10000000; $i++) {
    $objectStorage[] = Data::create(1, 200, 40);
}

echo "after loop \n";
echo memory_get_usage(true) / 1024 / 1024;
echo "\n";

you might wonder about the way I constructed it. I tested it in multiple notation this is utilizing the prototype pattern.

It just makes it faster, since the whole constructor routine and instantiation will be reduced to s a simple 'copy' in the low level API

<?php

declare(strict_types=1);

echo "before loop \n";
echo memory_get_usage(true) / 1024 / 1024;
echo "\n";

$objectStorage = [];
for ($i = 0; $i < 10000000; $i++) {

    $arr = \SplFixedArray::fromArray([1, 200, 40]);
    $objectStorage[] = $arr;
}

echo "after loop \n";
echo memory_get_usage(true) / 1024 / 1024;
echo "\n";

and the array one

<?php

echo "before loop \n";
echo memory_get_usage(true) / 1024 / 1024;
echo "\n";

$objectStorage = [];
for ($i = 0; $i < 10000000; $i++) {
    $objectStorage[] = [1, 200, 40];
}

echo "after loop \n";
echo memory_get_usage(true) / 1024 / 1024;
echo "\n";

to the results in order

DataObject

before loop 
2
after loop 
1574.00390625

FixedArray

before loop 
2
after loop 
2724.00390625

array

before loop 
2
after loop 
514.00390625

so data objects seem to be x3 and the fixed array even x5 bigger to me this is pretty drastic

ofc I looked into the opcodes for the different scripts

DataObject script

Finding entry points
Branch analysis from position: 0
1 jumps found. (Code = 42) Position 1 = 21
Branch analysis from position: 21
2 jumps found. (Code = 44) Position 1 = 23, Position 2 = 12
Branch analysis from position: 23
1 jumps found. (Code = 62) Position 1 = -2
Branch analysis from position: 12
2 jumps found. (Code = 44) Position 1 = 23, Position 2 = 12
Branch analysis from position: 23
Branch analysis from position: 12
filename:       /home/j/development/repos/data-structure-test/tuple-test.php
function name:  (null)
number of ops:  32
compiled vars:  !0 = $objectStorage, !1 = $i
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   5     0  E >   NOP                                                      
  28     1        ECHO                                                     'before+loop+%0A'
  29     2        INIT_FCALL                                               'memory_get_usage'
         3        SEND_VAL                                                 <true>
         4        DO_ICALL                                         $3      
         5        DIV                                              ~4      $3, 1024
         6        DIV                                              ~5      ~4, 1024
         7        ECHO                                                     ~5
  30     8        ECHO                                                     '%0A'
  32     9        ASSIGN                                                   !0, <array>
  33    10        ASSIGN                                                   !1, 0
        11      > JMP                                                      ->21
  34    12    >   INIT_STATIC_METHOD_CALL                                  'Data', 'create'
        13        SEND_VAL                                                 1
        14        SEND_VAL                                                 200
        15        SEND_VAL                                                 40
        16        DO_UCALL                                         $9      
        17        ASSIGN_DIM                                               !0
        18        OP_DATA                                                  $9
  33    19        POST_INC                                         ~10     !1
        20        FREE                                                     ~10
        21    >   IS_SMALLER                                       ~11     !1, 10000000
        22      > JMPNZ                                                    ~11, ->12
  37    23    >   ECHO                                                     'after+loop+%0A'
  38    24        INIT_FCALL                                               'memory_get_usage'
        25        SEND_VAL                                                 <true>
        26        DO_ICALL                                         $12     
        27        DIV                                              ~13     $12, 1024
        28        DIV                                              ~14     ~13, 1024
        29        ECHO                                                     ~14
  39    30        ECHO                                                     '%0A'
        31      > RETURN                                                   1

branch: #  0; line:     5-   33; sop:     0; eop:    11; out0:  21
branch: # 12; line:    34-   33; sop:    12; eop:    20; out0:  21
branch: # 21; line:    33-   33; sop:    21; eop:    22; out0:  23; out1:  12; out2:  23; out3:  12
branch: # 23; line:    37-   39; sop:    23; eop:    31; out0:  -2
path #1: 0, 21, 23, 
path #2: 0, 21, 12, 21, 23, 
path #3: 0, 21, 12, 21, 23, 
path #4: 0, 21, 23, 
path #5: 0, 21, 12, 21, 23, 
path #6: 0, 21, 12, 21, 23, 
Class Data:
Function create:
Finding entry points
Branch analysis from position: 0
2 jumps found. (Code = 43) Position 1 = 6, Position 2 = 10
Branch analysis from position: 6
1 jumps found. (Code = 62) Position 1 = -2
Branch analysis from position: 10
filename:       /home/j/development/repos/data-structure-test/tuple-test.php
function name:  create
number of ops:  21
compiled vars:  !0 = $a, !1 = $b, !2 = $c, !3 = $retVal
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
  13     0  E >   RECV                                             !0      
         1        RECV                                             !1      
         2        RECV                                             !2      
  14     3        FETCH_STATIC_PROP_R          global              $4      'template'
         4        BOOL_NOT                                         ~5      $4
         5      > JMPZ                                                     ~5, ->10
  15     6    >   NEW                                              $7      :27
         7        DO_FCALL                                      0          
         8        FETCH_STATIC_PROP_W          global              $6      'template'
         9        ASSIGN                                                   $6, $7
  18    10    >   FETCH_STATIC_PROP_R          global              $10     'template'
        11        CLONE                                            ~11     $10
        12        ASSIGN                                                   !3, ~11
  19    13        ASSIGN_OBJ                                               !3, 'a'
        14        OP_DATA                                                  !0
  20    15        ASSIGN_OBJ                                               !3, 'b'
        16        OP_DATA                                                  !1
  21    17        ASSIGN_OBJ                                               !3, 'v'
        18        OP_DATA                                                  !2
  24    19      > RETURN                                                   !3
  25    20*     > RETURN                                                   null

branch: #  0; line:    13-   14; sop:     0; eop:     5; out0:   6; out1:  10
branch: #  6; line:    15-   18; sop:     6; eop:     9; out0:  10
branch: # 10; line:    18-   25; sop:    10; eop:    20
path #1: 0, 6, 10, 
path #2: 0, 10, 
End of function create

SPLFixedArray script

function name:  (null)
number of ops:  30
compiled vars:  !0 = $objectStorage, !1 = $i, !2 = $arr
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   5     0  E >   ECHO                                                     'before+loop+%0A'
   6     1        INIT_FCALL                                               'memory_get_usage'
         2        SEND_VAL                                                 <true>
         3        DO_ICALL                                         $3      
         4        DIV                                              ~4      $3, 1024
         5        DIV                                              ~5      ~4, 1024
         6        ECHO                                                     ~5
   7     7        ECHO                                                     '%0A'
   9     8        ASSIGN                                                   !0, <array>
  10     9        ASSIGN                                                   !1, 0
        10      > JMP                                                      ->19
  12    11    >   INIT_STATIC_METHOD_CALL                                  'SplFixedArray', 'fromArray'
        12        SEND_VAL                                                 <array>
        13        DO_FCALL                                      0  $8      
        14        ASSIGN                                                   !2, $8
  13    15        ASSIGN_DIM                                               !0
        16        OP_DATA                                                  !2
  10    17        POST_INC                                         ~11     !1
        18        FREE                                                     ~11
        19    >   IS_SMALLER                                       ~12     !1, 10000000
        20      > JMPNZ                                                    ~12, ->11
  16    21    >   ECHO                                                     'after+loop+%0A'
  17    22        INIT_FCALL                                               'memory_get_usage'
        23        SEND_VAL                                                 <true>
        24        DO_ICALL                                         $13     
        25        DIV                                              ~14     $13, 1024
        26        DIV                                              ~15     ~14, 1024
        27        ECHO                                                     ~15
  18    28        ECHO                                                     '%0A'
        29      > RETURN                                                   1

branch: #  0; line:     5-   10; sop:     0; eop:    10; out0:  19
branch: # 11; line:    12-   10; sop:    11; eop:    18; out0:  19
branch: # 19; line:    10-   10; sop:    19; eop:    20; out0:  21; out1:  11; out2:  21; out3:  11
branch: # 21; line:    16-   18; sop:    21; eop:    29; out0:  -2
path #1: 0, 19, 21, 
path #2: 0, 19, 11, 19, 21, 
path #3: 0, 19, 11, 19, 21, 
path #4: 0, 19, 21, 
path #5: 0, 19, 11, 19, 21, 
path #6: 0, 19, 11, 19, 21,

Array script

Finding entry points
Branch analysis from position: 0
1 jumps found. (Code = 42) Position 1 = 15
Branch analysis from position: 15
2 jumps found. (Code = 44) Position 1 = 17, Position 2 = 11
Branch analysis from position: 17
1 jumps found. (Code = 62) Position 1 = -2
Branch analysis from position: 11
2 jumps found. (Code = 44) Position 1 = 17, Position 2 = 11
Branch analysis from position: 17
Branch analysis from position: 11
filename:       /home/j/development/repos/data-structure-test/array-test.php
function name:  (null)
number of ops:  26
compiled vars:  !0 = $objectStorage, !1 = $i
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   3     0  E >   ECHO                                                     'before+loop+%0A'
   4     1        INIT_FCALL                                               'memory_get_usage'
         2        SEND_VAL                                                 <true>
         3        DO_ICALL                                         $2      
         4        DIV                                              ~3      $2, 1024
         5        DIV                                              ~4      ~3, 1024
         6        ECHO                                                     ~4
   5     7        ECHO                                                     '%0A'
   7     8        ASSIGN                                                   !0, <array>
   8     9        ASSIGN                                                   !1, 0
        10      > JMP                                                      ->15
   9    11    >   ASSIGN_DIM                                               !0
        12        OP_DATA                                                  <array>
   8    13        POST_INC                                         ~8      !1
        14        FREE                                                     ~8
        15    >   IS_SMALLER                                       ~9      !1, 10000000
        16      > JMPNZ                                                    ~9, ->11
  12    17    >   ECHO                                                     'after+loop+%0A'
  13    18        INIT_FCALL                                               'memory_get_usage'
        19        SEND_VAL                                                 <true>
        20        DO_ICALL                                         $10     
        21        DIV                                              ~11     $10, 1024
        22        DIV                                              ~12     ~11, 1024
        23        ECHO                                                     ~12
  14    24        ECHO                                                     '%0A'
        25      > RETURN                                                   1

branch: #  0; line:     3-    8; sop:     0; eop:    10; out0:  15
branch: # 11; line:     9-    8; sop:    11; eop:    14; out0:  15
branch: # 15; line:     8-    8; sop:    15; eop:    16; out0:  17; out1:  11; out2:  17; out3:  11
branch: # 17; line:    12-   14; sop:    17; eop:    25; out0:  -2
path #1: 0, 15, 17, 
path #2: 0, 15, 11, 15, 17, 
path #3: 0, 15, 11, 15, 17, 
path #4: 0, 15, 17, 
path #5: 0, 15, 11, 15, 17, 
path #6: 0, 15, 11, 15, 17,

So we see some increased complexity in the normal object footprint but the other ones are pretty similar

To me this is an interesting find that needs further analysis ;D but right now i gotta cook some lunch :)

Comments (7)

Add a comment
Emil Moe's photo

So just to understand the numbers correct. You say that arrays are more efficient than the other 2?

Show all replies
Emil Moe's photo

Software Engineer & Consultant

Haha true. They created "Lumen" which is a variant for API that should be faster, and no one forces you to use Collections, so I guess that's the answer there.