Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decreasing memory usage for array and SplFixedArray

I was doing some benchmarks between array() and SplFixedArray() and I run into strange behaviour. At first, look at my simple test (it's actually just edited version from internet, sorry, I can't find original source now):

function formatMemoryUsage($usage) {
   $unit = array(' B', 'kB', 'MB', 'GB', 'TB');
   $factor = floor((strlen($usage) - 1) / 3);

   return sprintf('%.2f %s (%d bytes) ', $usage / pow(1024, $factor), $unit[$factor], $usage);
}

for($size = 1000; $size < 100000; $size *= 2) { 
   echo PHP_EOL . '> Testing size: ' . number_format($size) . PHP_EOL; 
   echo '   Array()' . PHP_EOL;

   for($s = microtime(true), $m = memory_get_usage(true), $container = Array(), $i = 0; $i < $size; $i++) $container[$i] = null; 
      echo '      - Write         - time   : ' . str_pad(microtime(true) - $s, 20, '0') . '     - memory: ' . formatMemoryUsage(memory_get_usage(true) - $m) . PHP_EOL; 

   $s = microtime(true); 
   foreach ($container as $key => $value) {
      $void = $value;
   }
   echo '      - Read          - time   : ' . str_pad(microtime(true) - $s, 20, '0') . PHP_EOL;

   unset($container);

   echo '   SplFixedArray()' . PHP_EOL;

   for($s = microtime(true), $m = memory_get_usage(true), $container = new SplFixedArray($size), $i = 0; $i < $size; $i++) $container[$i] = null; 
   echo '      - Write         - time   : ' . str_pad(microtime(true) - $s, 20, '0') . '     - memory: ' . formatMemoryUsage(memory_get_usage(true) - $m) . PHP_EOL; 

   $s = microtime(true); 
   foreach ($container as $key => $value) {
      $void = $value;
   }
   echo '      - Read          - time   : ' . str_pad(microtime(true) - $s, 20, '0') . PHP_EOL; 

   unset($container);
} 

Results were kind of expected - SplFixedArray() was faster in writing and little bit slower in reading. Things starts to be strange when I put another same SplFixedArray() test right after unset() of previous one, see the output:

> Testing size: 64,000
   Array()
      - Write         - time   : 0.009041070938110400     - memory: 7.50 MB (7864320 bytes) 
      - Read          - time   : 0.004010915756225600
   SplFixedArray()
      - Write         - time   : 0.004639148712158200     - memory: 1.75 MB (1835008 bytes) 
      - Read          - time   : 0.005971908569335900
   SplFixedArray()
      - Write         - time   : 0.005653858184814500     - memory: 1.50 MB (1572864 bytes) 
      - Read          - time   : 0.006288051605224600

Why second test use less memory than first one? And hey, I try to add next test and:

> Testing size: 64,000
   Array()
      - Write         - time   : 0.008963823318481400     - memory: 7.50 MB (7864320 bytes) 
      - Read          - time   : 0.004142045974731400
   SplFixedArray()
      - Write         - time   : 0.005026102066040000     - memory: 1.75 MB (1835008 bytes) 
      - Read          - time   : 0.005756139755249000
   SplFixedArray()
      - Write         - time   : 0.004483938217163100     - memory: 1.50 MB (1572864 bytes) 
      - Read          - time   : 0.005591869354248000
   SplFixedArray()
      - Write         - time   : 0.004633903503418000     - memory: 1.25 MB (1310720 bytes) 
      - Read          - time   : 0.005697011947631800

So I of course try to add more and more and after few more decreasing stoped on 512 kB. My question here is obvious: How it is possible and why that when I unset previous object and create new one, used memory is lower? And it works with normal array() too.

like image 412
Pavel Štěrba Avatar asked Nov 01 '22 06:11

Pavel Štěrba


1 Answers

We need to go into the engine if we want to understand this. Better: inside of Zend/zend_alloc.c (The Zend Memory Manager).

The memory allocated by the memory manager is split into chunks of 256 KB.

Upon freeing the SPLFixedArray, only the first contiguous chunk of memory is freed. There always remains a block of 256 KB (some variables) which then accumulates. (which works as the next from the OS allocated memory will be just adjacent to this memory block)

This memory segment is then marked as free and used the next time when possible instead of allocating new memory from the OS. (and if necessary some memory is appended)

But as at least one block of 256 KB is always freed, we always will notice a difference of 256 KB.


When you want to measure memory usage, I really would use memory_get_usage(false) as it indicates you how much memory PHP (≠ Zend) needs. (the only thing which counts against the memory_limit ini setting)

like image 123
bwoebi Avatar answered Nov 13 '22 16:11

bwoebi