PHP Objects vs Arrays -- Performance comparison while iterating

Viewed 72036

I have a huge amount of PHP objects for a neural network for which I have to iterate over and perform some maths on. I was wondering if I would be better off using an associative array over instances of classes?

I am dealing with around 3640 objects and iterating around 500 times (at best) on top of that so any micro-optimization helps a great deal. Would it inevitably be quicker to do $object['value'] than $object->value?

Edit: So they are both the same. But I guess there would be a little overhead for the constructor? Either way I don't think I want to trade in my beautiful classes for dirty arrays :P

11 Answers

magallanes' script @ PHP 7.3.5

  • SomeClass Object is fastest and lightest.
  • Array 1.32x speed. 2.70x memory.
  • stdClass Object 1.65x speed. 2.94x memory.

Raw output:

arrays: 0.064794063568115
memory: 444920
Array (
    [aaa] => aaa
    [bbb] => bbb
    [ccc] => aaabbb
)
arrays: 0.048975944519043
memory: 164512
SomeClass Object (
    [aaa] => aaa
    [bbb] => bbb
    [ccc] => aaabbb
)
arrays: 0.081161022186279
memory: 484416
stdClass Object (
    [aaa] => aaa
    [bbb] => bbb
    [ccc] => aaabbb
)

Well I got curious today based off of @magallanes benchmark, so I expanded it a bit. I upped some of the for loops to really highlight the gaps between things. This is running on Apache 2.4, mod_php, and PHP 7.2.

Here's a summary table to make the results easier:

+---------------------------+---------+-----------------+
|           Test            | Memory  |      Time       |
+---------------------------+---------+-----------------+
| Array                     | 2305848 | 9.5637300014496 |
| stdClass                  | 2505824 | 11.212271928787 |
| SomeClass                 |  963640 | 11.558017015457 | <-- *
| AnotherClass              | 2563136 | 10.872401237488 |
| SetterClass               |  905848 | 59.879059791565 |
| SetterClassDefineReturn   |  905792 | 60.484427213669 |
| SetterClassSetFromParam   |  745792 | 62.783381223679 |
| SetterClassSetKeyAndParam |  745824 | 72.155715942383 |
+---------------------------+---------+-----------------+
* - Winner winner chicken dinner

Below is the modified script. I wanted to test setting properties with methods and defining types. I was very surprised to find that using setter methods adds a significant hit to the code. Now granted this is a very very specific performance test where many apps will not even hit this. But if you have a site that handles 1000/reqs/second with 1000 classes that are used with 1000s of objects, you can see how this may affect performance.

<?php

set_time_limit(500);

class SomeClass {
    public $aaa;
    public $bbb;
    public $ccc;
}
    
class AnotherClass {
}

class SetterClass {
    public $aaa;
    public $bbb;
    public $ccc;

    public function setAAA() {
        $this->aaa = 'aaa';
    }

    public function setBBB() {
        $this->bbb = 'bbb';
    }

    public function setCCC() {
        $this->ccc = $this->aaa.$this->bbb;
    }
}

class SetterClassDefineReturn {
    public $aaa;
    public $bbb;
    public $ccc;

    public function setAAA():void {
        $this->aaa = 'aaa';
    }

    public function setBBB():void {
        $this->bbb = 'bbb';
    }

    public function setCCC():void {
        $this->ccc = $this->aaa.$this->bbb;
    }
}

class SetterClassSetFromParam {
    public $aaa;
    public $bbb;
    public $ccc;

    public function setAAA(string $val): void {
        $this->aaa = $val;
    }

    public function setBBB(string $val): void {
        $this->bbb = $val;
    }

    public function setCCC(string $val): void {
        $this->ccc = $val;
    }
}

class SetterClassSetKeyAndParam {
    public $aaa;
    public $bbb;
    public $ccc;

    public function set(string $key, string $val): void {
        $this->{$key} = $val;
    }
}

function p($i) {
  echo '<pre>';
  print_r($i);
  echo '</pre>';
  echo '<hr>';
}

$t0 = microtime(true);
$arraysOf=[];
$inicio=memory_get_usage(); 
for ($i=0; $i<5000; $i++) {
    $z = new SomeClass();
    for ($j=0; $j<5000; $j++) {
        $z->aaa = 'aaa';
        $z->bbb = 'bbb';
        $z->ccc = $z->aaa.$z->bbb;          
    }
    $arraysOf[]=$z;
}
$fin=memory_get_usage();    
echo '<p>Time Taken (seconds): '.(microtime(true) - $t0).'</p>';
echo '<p>Memory: '.($fin-$inicio).'</p>';
p($z);

$t0 = microtime(true);
$arraysOf=[];
$inicio=memory_get_usage(); 
for ($i=0; $i<5000; $i++) {
    $z = new AnotherClass();
    for ($j=0; $j<5000; $j++) {
        $z->aaa = 'aaa';
        $z->bbb = 'bbb';
        $z->ccc = $z->aaa.$z->bbb;          
    }
    $arraysOf[]=$z;
}
$fin=memory_get_usage();    
echo '<p>Time Taken (seconds): '.(microtime(true) - $t0).'</p>';
echo '<p>Memory: '.($fin-$inicio).'</p>';
p($z);

$t0 = microtime(true);
$arraysOf=[];
$inicio=memory_get_usage(); 
for ($i=0; $i<5000; $i++) {
    $z = new SetterClass();
    for ($j=0; $j<5000; $j++) {
        $z->setAAA();
        $z->setBBB();
        $z->setCCC();          
    }
    $arraysOf[]=$z;
}
$fin=memory_get_usage();    
echo '<p>Time Taken (seconds): '.(microtime(true) - $t0).'</p>';
echo '<p>Memory: '.($fin-$inicio).'</p>';
p($z);

$t0 = microtime(true);
$arraysOf=[];
$inicio=memory_get_usage(); 
for ($i=0; $i<5000; $i++) {
    $z = new SetterClassDefineReturn();
    for ($j=0; $j<5000; $j++) {
        $z->setAAA();
        $z->setBBB();
        $z->setCCC();          
    }
    $arraysOf[]=$z;
}
$fin=memory_get_usage();    
echo '<p>Time Taken (seconds): '.(microtime(true) - $t0).'</p>';
echo '<p>Memory: '.($fin-$inicio).'</p>';
p($z);

$t0 = microtime(true);
$arraysOf=[];
$inicio=memory_get_usage(); 
for ($i=0; $i<5000; $i++) {
    $z = new SetterClassSetFromParam();
    for ($j=0; $j<5000; $j++) {
        $z->setAAA('aaa');
        $z->setBBB('bbb');
        $z->setCCC('aaabbb');          
    }
    $arraysOf[]=$z;
}
$fin=memory_get_usage();    
echo '<p>Time Taken (seconds): '.(microtime(true) - $t0).'</p>';
echo '<p>Memory: '.($fin-$inicio).'</p>';

p($z);

$t0 = microtime(true);
$arraysOf=[];
$inicio=memory_get_usage(); 
for ($i=0; $i<5000; $i++) {
    $z = new SetterClassSetKeyAndParam();
    for ($j=0; $j<5000; $j++) {
        $z->set('aaa', 'aaa');
        $z->set('bbb', 'bbb');  
        $z->set('ccc', 'aaabbb');        
    }
    $arraysOf[]=$z;
}
$fin=memory_get_usage();    
echo '<p>Time Taken (seconds): '.(microtime(true) - $t0).'</p>';
echo '<p>Memory: '.($fin-$inicio).'</p>';
p($z);

$t0 = microtime(true);
$arraysOf=[];
$inicio=memory_get_usage(); 
for ($i=0; $i<5000; $i++) {
    $z = new stdClass();
    for ($j=0; $j<5000; $j++) {
        $z->aaa = 'aaa';
        $z->bbb = 'bbb';
        $z->ccc = $z->aaa.$z->bbb;          
    }
    $arraysOf[]=$z;
}
$fin=memory_get_usage();    
echo '<p>Time Taken (seconds): '.(microtime(true) - $t0).'</p>';
echo '<p>Memory: '.($fin-$inicio).'</p>';
p($z); 


$t0 = microtime(true);
$arraysOf=[];
$inicio=memory_get_usage(); 
for ($i=0; $i<5000; $i++) {
    $z = [];
    for ($j=0; $j<5000; $j++) {
        $z['aaa'] = 'aaa';
        $z['bbb'] = 'bbb';
        $z['ccc'] = $z['aaa'].$z['bbb'];            
    }
    $arraysOf[]=$z;
}
$fin=memory_get_usage();    
echo '<p>Time Taken (seconds): '.(microtime(true) - $t0).'</p>';
echo '<p>Memory: '.($fin-$inicio).'</p>';
p($z);

And here's the results:

Time Taken (seconds): 11.558017015457

Memory: 963640

SomeClass Object
(
    [aaa] => aaa
    [bbb] => bbb
    [ccc] => aaabbb
)

-----

Time Taken (seconds): 10.872401237488

Memory: 2563136

AnotherClass Object
(
    [aaa] => aaa
    [bbb] => bbb
    [ccc] => aaabbb
)

----

Time Taken (seconds): 59.879059791565

Memory: 905848

SetterClass Object
(
    [aaa] => aaa
    [bbb] => bbb
    [ccc] => aaabbb
)

----

Time Taken (seconds): 60.484427213669

Memory: 905792

SetterClassDefineReturn Object
(
    [aaa] => aaa
    [bbb] => bbb
    [ccc] => aaabbb
)

----

Time Taken (seconds): 62.783381223679

Memory: 745792

SetterClassSetFromParam Object
(
    [aaa] => aaa
    [bbb] => bbb
    [ccc] => aaabbb
)

----

Time Taken (seconds): 72.155715942383

Memory: 745824

SetterClassSetKeyAndParam Object
(
    [aaa] => aaa
    [bbb] => bbb
    [ccc] => aaabbb
)

----

Time Taken (seconds): 11.212271928787

Memory: 2505824

stdClass Object
(
    [aaa] => aaa
    [bbb] => bbb
    [ccc] => aaabbb
)

----

Time Taken (seconds): 9.5637300014496

Memory: 2305848

Array
(
    [aaa] => aaa
    [bbb] => bbb
    [ccc] => aaabbb
)

For anybody who is still interested in this question :) I ran Quazzle code on PHP 7.1 Ubuntu x64 and got this answer:

arrays: 0.24848890304565

memory: 444920

Array
(
    [aaa] => aaa
    [bbb] => bbb
    [ccc] => aaabbb
)
arrays: 0.23238587379456

memory: 164512

SomeClass Object
(
    [aaa] => aaa
    [bbb] => bbb
    [ccc] => aaabbb
)
arrays: 0.24422693252563

memory: 484416

stdClass Object
(
    [aaa] => aaa
    [bbb] => bbb
    [ccc] => aaabbb
)

Conclusion

Array take 4(!) the memory than class object.
Class object marginally faster.
stdClass still evil © magallanes :)

You haven't shown us the code for how $object->value works, as it could be that backend it is an array in which case theoretically using an array would be faster as it involves one less function call. The cost of doing the lookup will probably be huge compared to the function call. If it is a variable, there is going to be very little diffrence as objects and arrays in PHP have a very similar implementation.

If you are looking at optimizations, you will need to profile to check where the majority of the time is being used. I suspect that changing objects to arrays will make no major difference.

You can always check the PHP-source code for micro-performance-features like that.

But at a first glance, no doing ['value'] will not be faster because PHP needs to do a Lookup on where to find ['value'] even thougn a hashtable lookup should be O(1), that's not guaranteed. There's more overhead when you use Text-index.

If the object only contains 1 variables that you need to access which is value, there's more overhead in using an object.

To be fair it depends on what sort of data You have. The results in other answers are (more or less)* correct. I'll paste in the test code on the bottom for clarity.

The important thing here is that You can save a fair amount of memory if you use a predefined object, the trade-off is the fact that you need to define the exact structure.

Consider the following test results:

static obj: 0.04223108291626
memory: 164920
object(staticSet)#1 (3) {
  ["aaa"]=>
  string(3) "aaa"
  ["bbb"]=>
  string(3) "bbb"
  ["ccc"]=>
  string(6) "aaabbb"
}
---------------------------------------
static obj with an additional prop: 0.042968988418579
memory: 524920
object(staticSet2)#1000 (3) {
  ["aaa"]=>
  string(3) "aaa"
  ["bbb"]=>
  string(3) "bbb"
  ["ddd"]=>
  string(6) "aaabbb"
}
---------------------------------------

I might be wrong here, I'm did not look into the PHP source but I believe that as far as PHP is concerned, everything is an array. A predefined object is just a well defined array. An interesting note is that the value type does not have an effect ie. a string value of '1' will take up the same amount of memory as a bool value of true.

Why? Well as I stated before PHP is arrays so if you have an object with a setter function what (a simplified version) PHP does is this:

$objects = ['objectName' => [
    'aaa' => '',
    'bbb' => '',
    'ccc' => '',
    'setValue:f' => function('name', 'value') { ... }
    ]
];

Every time you execute, you do a lookup for the function key that does a lookup for a prop name, plus any validation for prop type etc. in the background. The memory savings in a predefined object come from defining an array of a set length (otherwise impossible in PHP) instead of allocating a chunk of memory.

Conclusion:

Static objects: Yey! We save memory! But if you try to add anything outside the definition it will grow larger then a stdClass.

['aaa'] into [['aaa'],['bbb']]

Arrays: If You write in PHP You better love them, even a string is an array of characters in PHP.

Objects: Objects have cool functionality, they're a wonderful way to keep your code organised, clean, modular. But as we all know "with great power...".

Finally, my test code and the '*'. people seem to forget that if You don't clean up your variables while the script is running they will remain in the memory, while it will not impact the execution time (much), it will impact the memory measurement. So while running a memory profiling unset() is your friend.

<?php

error_reporting(E_ALL);
ini_set('display_errors',1);

$noOfObjects = 1000;
$noOfOperations = 1000;

class set {
    public function setKey($key, $value) {
        $this->{$key} = $value;
    }
    public function readKey($name) {
        return $this->{$name};
    }
}

class staticSet {
    public string $aaa;
    public string $bbb;
    public string $ccc;
}

class staticSet2 {
    public string $aaa;
    public string $bbb;
}

class staticSet3 {
    public bool $aaa;
    public bool $bbb;
    public bool $ccc;
}

$arraysOf=[];
$t0 = microtime(true);
$start=memory_get_usage(); 
for ($i=0; $i<$noOfObjects; $i++) {
    $z = array(); // definition
    for ($j=0; $j<$noOfOperations; $j++) {
        $z['aaa'] = 'aaa';
        $z['bbb'] = 'bbb';
        $z['ccc'] = $z['aaa'].$z['bbb'];
    }
    $arraysOf[]=$z;
}
$end=memory_get_usage();  
echo 'arrays: '.(microtime(true) - $t0).PHP_EOL;
echo 'memory: '.($end-$start).PHP_EOL;
var_dump($z);
unset($t0,$start,$end,$z,$arraysOf);
echo '---------------------------------------'.PHP_EOL;
$arraysOf=[];
$t0 = microtime(true);
$start=memory_get_usage(); 
for ($i=0; $i<$noOfObjects; $i++) {
    $z = new set(); // definition
    for ($j=0; $j<$noOfOperations; $j++) {
        $z->setKey('aaa','aaa');
        $z->setKey('bbb','bbb');
        $z->setKey('ccc',$z->readKey('aaa').$z->readKey('bbb'));
    }
    $arraysOf[]=$z;
}
$end=memory_get_usage();  
echo 'dynamic obj: '.(microtime(true) - $t0).PHP_EOL;
echo 'memory: '.($end-$start).PHP_EOL;
var_dump($z);
unset($t0,$start,$end,$z,$arraysOf);
echo '---------------------------------------'.PHP_EOL;
$arraysOf=[];
$t0 = microtime(true);
$start=memory_get_usage(); 
for ($i=0; $i<$noOfObjects; $i++) {
    $z = new staticSet(); // definition
    for ($j=0; $j<$noOfOperations; $j++) {
        $z->{'aaa'} = 'aaa';
        $z->{'bbb'} = 'bbb';
        $z->{'ccc'} = $z->{'aaa'}.$z->{'bbb'};
    }
    $arraysOf[]=$z;
}
$end=memory_get_usage();  
echo 'static obj: '.(microtime(true) - $t0).PHP_EOL;
echo 'memory: '.($end-$start).PHP_EOL;
var_dump($z);
unset($t0,$start,$end,$z,$arraysOf);
echo '---------------------------------------'.PHP_EOL;
$arraysOf=[];
$t0 = microtime(true);
$start=memory_get_usage(); 
for ($i=0; $i<$noOfObjects; $i++) {
    $z = new staticSet2(); // definition
    for ($j=0; $j<$noOfOperations; $j++) {
        $z->{'aaa'} = 'aaa';
        $z->{'bbb'} = 'bbb';
        $z->{'ddd'} = $z->{'aaa'}.$z->{'bbb'};
    }
    $arraysOf[]=$z;
}
$end=memory_get_usage();  
echo 'static obj with an additional prop: '.(microtime(true) - $t0).PHP_EOL;
echo 'memory: '.($end-$start).PHP_EOL;
var_dump($z);
unset($t0,$start,$end,$z,$arraysOf);
echo '---------------------------------------'.PHP_EOL;
$arraysOf=[];
$t0 = microtime($noOfObjects);
$start=memory_get_usage(); 
for ($i=0; $i<$noOfObjects; $i++) {
    $z = new Ds\Map(); // definition
    for ($j=0; $j<$noOfOperations; $j++) {
        $z->put('aaa','aaa');
        $z->put('bbb','bbb');
        $z->put('ccc',$z->get('aaa').$z->get('bbb'));
    }
    $arraysOf[]=$z;
}
$end=memory_get_usage();  
echo 'Ds\Map: '.(microtime(true) - $t0).PHP_EOL;
echo 'memory: '.($end-$start).PHP_EOL;
var_dump($z);
unset($t0,$start,$end,$z,$arraysOf);
echo '---------------------------------------'.PHP_EOL;
$arraysOf=[];
$t0 = microtime(true);
$start=memory_get_usage(); 
for ($i=0; $i<$noOfObjects; $i++) {
    $z = new staticSet(); // definition
    for ($j=0; $j<$noOfOperations; $j++) {
        $z->{'aaa'} = '1';
        $z->{'bbb'} = '1';
        $z->{'ccc'} = ($z->{'aaa'} && $z->{'bbb'});
    }
    $arraysOf[]=$z;
}
$end=memory_get_usage();  
echo 'static obj with bool values: '.(microtime(true) - $t0).PHP_EOL;
echo 'memory: '.($end-$start).PHP_EOL;
var_dump($z);
unset($t0,$start,$end,$z,$arraysOf);
echo '---------------------------------------'.PHP_EOL;
$arraysOf=[];
$t0 = microtime(true);
$start=memory_get_usage(); 
for ($i=0; $i<$noOfObjects; $i++) {
    $z = new staticSet3(); // definition
    for ($j=0; $j<$noOfOperations; $j++) {
        $z->{'aaa'} = '1';
        $z->{'bbb'} = '1';
        $z->{'ccc'} = ($z->{'aaa'} && $z->{'bbb'});
    }
    $arraysOf[]=$z;
}
$end=memory_get_usage();  
echo 'static obj with defined bool values: '.(microtime(true) - $t0).PHP_EOL;
echo 'memory: '.($end-$start).PHP_EOL;
var_dump($z);
unset($t0,$start,$end,$z,$arraysOf);
echo '---------------------------------------'.PHP_EOL;
$arraysOf=[];
$t0 = microtime(true);
$start=memory_get_usage(); 
for ($i=0; $i<$noOfObjects; $i++) {
    $z = array('aaa' => '','bbb' => '','ccc' => ''); // definition
    for ($j=0; $j<$noOfOperations; $j++) {
        $z['aaa'] = 'aaa';
        $z['bbb'] = 'bbb';
        $z['ccc'] = $z['aaa'].$z['bbb'];
    }
    $arraysOf[]=$z;
}
$end=memory_get_usage();  
echo 'predefined array: '.(microtime(true) - $t0).PHP_EOL;
echo 'memory: '.($end-$start).PHP_EOL;
var_dump($z);
unset($t0,$start,$end,$z,$arraysOf);
echo '---------------------------------------'.PHP_EOL;


echo 'phpversion '.phpversion().PHP_EOL;
Related