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 :)

j

I am thinking ...

Write your comment…

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

Show all replies

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.

Reply to this…