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