13

If I have an array with multiple keys and values, like this:

$array = array(
  'key1' => 'value1',
  'key2' => 'value2',
);

Are there any best-practices on how to loop through an array when I only use the keys?

Possible solution 1:

foreach(array_keys($array) as $array_key) {
  echo $array_key;
}

Possible solution 2:

foreach($array as $array_key => $array_value) {
  echo $array_key;
}

I can see the advantage in solution 1 as it doesn't include the unused variable $array_value. Is there a difference in performace (speed, memory allocation, etc.) between these two?

Raphael Portmann
  • 189
  • 1
  • 2
  • 7
  • 5
    _"It there a difference in performance between these two?"_ That would be very simple to test. – Alex Howansky Nov 16 '17 at 16:51
  • 2
    I think solution 2 will be faster, because in solution 1 you are calling `array_keys()` on every iteration of the loop. – GrumpyCrouton Nov 16 '17 at 16:52
  • 1
    I think that the amount of difference this would make would depend very much on the size of the array. – Don't Panic Nov 16 '17 at 16:53
  • 1
    @GrumpyCrouton I don't think that's true, I think it's just evaluated once at the beginning. – Don't Panic Nov 16 '17 at 16:53
  • 1
    I'd say you are correct grumpy. But that can be solved by doing array_keys before the foreach. – Andreas Nov 16 '17 at 16:53
  • 1
    @Andreas Very true, then they would be exactly the same probably. – GrumpyCrouton Nov 16 '17 at 16:55
  • 1
    The first version is going to end up with a temporary copy of the array keys in memory while you loop over them. Your array would need to be pretty huge for that to be a problem, though. See https://eval.in/901754 – iainn Nov 16 '17 at 16:55
  • @Don'tPanic I could be wrong too, that was an assumption based on for loops. – GrumpyCrouton Nov 16 '17 at 16:57
  • For reference, `array_keys` will only be called once. See https://eval.in/901760 – iainn Nov 16 '17 at 16:57
  • I thought option 2 would be quicker, but tried it on an array with a million k=>v pairs and option 1 was about 10% faster for me – miknik Nov 16 '17 at 17:02
  • 2
    Following @iainn's comment, a more developed response to foreach and copy / no copy: https://stackoverflow.com/questions/10057671/how-does-php-foreach-actually-work – NaeiKinDus Nov 16 '17 at 17:04
  • 2
    @GrumpyCrouton This _might_ help explain why array_keys only gets called once: https://stackoverflow.com/a/14854568/4458445 – RToyo Nov 16 '17 at 17:04

1 Answers1

17

Warning: the benchmark below is not accurate that makes its results wrong.

From my quick and dirty benchmark I did the following 50 times:

Solution 1 (worst):

foreach (array_keys($array) as $array_key) {
  echo $array_key;
}

Size of array: 1000000

  • Min: 0.11363291740417
  • Avg: 0.11681462287903
  • Max: 0.14569497108459

Size of array: 9999999

  • Min: 1.3098199367523
  • Avg: 1.3250577354431
  • Max: 1.3673560619354

Solution 2 (middle):

foreach ($array as $array_key => $array_value) {
  echo $array_key;
}

Size of array: 1000000

  • Min: 0.10167503356934
  • Avg: 0.10356130123138
  • Max: 0.11027193069458

Size of array: 9999999

  • Min: 1.2077870368958
  • Avg: 1.2256314325333
  • Max: 1.2829539775848

Solution 3 (best):

$array_keys = array_keys($array);
foreach ($array_keys as $array_key) {
  echo $array_key;
}

Size of array: 1000000

  • Min: 0.090911865234375
  • Avg: 0.092938437461853
  • Max: 0.097810983657837

Size of array: 9999999

  • Min: 1.0793349742889
  • Avg: 1.0941110134125
  • Max: 1.1402878761292

So you can see solution 3 is the quickest option if you only want to look through array keys :)

Hope this helps.


Code:

<?php
class DirtyBenchmarker {
  private $results = [];
  private $size_of_array;

  public function __construct($size_of_array)
  {
    $this->size_of_array = $size_of_array;
    echo 'Size of array: ' .  $this->size_of_array . PHP_EOL;
  }
  
  private function solution1() {
    $array = range(0, $this->size_of_array - 1);
    ob_start();
    $start = microtime(true);
    foreach (array_keys($array) as $array_key) {
      echo $array_key;
    }
    $finish = microtime(true) - $start;
    $echod = ob_get_clean();
    $this->results['solution1'][] = $finish;
  }

  private function solution2() {
    $array = range(0, $this->size_of_array - 1);
    ob_start();
    $start = microtime(true);
    foreach ($array as $array_key => $array_value) {
      echo $array_key;
    }
    $finish = microtime(true) - $start;
    $echod = ob_get_clean();
    $this->results['solution2'][] = $finish;
  }

  private function solution3() {
    $array = range(0, $this->size_of_array - 1);
    $array_keys = array_keys($array);
    ob_start();
    $start = microtime(true);
    foreach ($array_keys as $array_key) {
      echo $array_key;
    }
    $finish = microtime(true) - $start;
    $echod = ob_get_clean();
    $this->results['solution3'][] = $finish;
  }

  public function benchmark() {
    $this->solution1();
    $this->solution2();
    $this->solution3();
  }

  public function getResults()
  {
    echo PHP_EOL . 'Solution 1:' . PHP_EOL;
    echo 'Min: ' . min($this->results['solution1']) . PHP_EOL;
    echo 'Avg: ' . array_sum($this->results['solution1']) / count($this->results['solution1']) . PHP_EOL;
    echo 'Max: ' . max($this->results['solution1']) . PHP_EOL;

    echo PHP_EOL . 'Solution 2:' . PHP_EOL;
    echo 'Min: ' . min($this->results['solution2']) . PHP_EOL;
    echo 'Avg: ' . array_sum($this->results['solution2']) / count($this->results['solution2']) . PHP_EOL;
    echo 'Max: ' . max($this->results['solution2']) . PHP_EOL;

    echo PHP_EOL . 'Solution 3:' . PHP_EOL;
    echo 'Min: ' . min($this->results['solution3']) . PHP_EOL;
    echo 'Avg: ' . array_sum($this->results['solution3']) / count($this->results['solution3']) . PHP_EOL;
    echo 'Max: ' . max($this->results['solution3']) . PHP_EOL;
  }
}

$benchmarker = new DirtyBenchmarker(1000000);
$runs = 50;
for ($i = 0; $i < $runs; $i++) {
  $benchmarker->benchmark();
}
$benchmarker->getResults();

$benchmarker = new DirtyBenchmarker(9999999);
$runs = 50;
for ($i = 0; $i < $runs; $i++) {
  $benchmarker->benchmark();
}
$benchmarker->getResults();
Your Common Sense
  • 154,967
  • 38
  • 205
  • 325
alistaircol
  • 1,387
  • 10
  • 21
  • 1
    I know this is super old but heaven forbid someone uses this. Solution 3 is not the best. It allocates RAM for the array of keys and your test doesn't include the time to do that or to deallocate the RAM used by the new array (which solution 1 includes implicitly). Solution 2 is the best. – Knyri May 20 '21 at 14:22
  • Just came to say this. I don't know which PHP version was used here, probably 4.3 or something. Because array_keys() requires PHP to perform **two loops**, one to get keys and another to actually loop over them. Doubling the memory footprint as well. While foreach obviously loops once. One of those misinformation answers we all hate Stack Overflow for. – Your Common Sense May 16 '22 at 08:54
  • Actually this "benchmark" is loaded, intentional or not. For some reason $array_keys = array_keys($array); is not benchmarked, spoiling the result. – Your Common Sense May 16 '22 at 08:56