Monday, August 3, 2015

For or Foreach? PHP vs. Javascript, C++, Java, HHVM (update: Go, Zephir)

Lessons learned:
  • Foreach is 4-5 times faster than For
  • Nested Foreach is 2-3 times faster than nested For
  • Foreach with key lookup is 2-3 times slower than Foreach without
  • C++ is 5-300 times faster than PHP running For/Foreach on Arrays
  • HHVM is 2-3 times faster than PHP
  • PHP 7 is 2-4 times faster than PHP 5.5
  • HHVM is currently no alternative to C++
  • Javascript is 2-20 times slower than C++/Java running For on nested Arrays
  • Go is 4-20 times faster than HHVM

Here is a sample script:
<?php
function test(){
// init arrays
$array = array();
for ($i=0; $i<50000; $i++) $array[] = $i*2;

$array2 = array();
for ($i=20000; $i<21000; $i++) $array2[] = $i*2;

// test1: foreach big-array (foreach small-array)
$start = microtime(true);
foreach ($array as $val) {
  foreach ($array2 as $val2) if ($val == $val2) {}
}
echo number_format(microtime(true)-$start, 2)."s\n";

// test1b: foreach big-array (foreach small-array)
$start = microtime(true);
foreach ($array as $val) {
  foreach ($array2 as $val2) if ($val === $val2) {}
}
echo number_format(microtime(true)-$start, 2)."s\n";

// test2: foreach small-array (foreach big-array)
$start = microtime(true);
foreach ($array2 as $val2) {
  foreach ($array as $val) if ($val == $val2) {}
}
echo number_format(microtime(true)-$start, 2)."s\n";

// test3: foreach big-array (foreach small-array) with key lookup
$start = microtime(true);
foreach ($array as $key=>$val) {
  foreach ($array2 as $key2=>$val2) if ($array[$key] == $array2[$key2]) {}
}
echo number_format(microtime(true)-$start, 2)."s\n";

// test4: foreach small-array (foreach big-array) with key lookup
$start = microtime(true);
foreach ($array2 as $key=>$val2) {
  foreach ($array as $val) if ($array[$key] == $array2[$key2]) {}
}
echo number_format(microtime(true)-$start, 2)."s\n";

// test5: for big-array (for small-array)
$start = microtime(true);
$count = count($array);
$count2 = count($array2);
for ($key=0; $key<$count; $key++) {
  for ($key2=0; $key2<$count2; $key2++) if ($array[$key] == $array2[$key2]) {}
}
echo number_format(microtime(true)-$start, 2)."s\n";

// test6: for small-array (for big-array)
$start = microtime(true);
$count = count($array);
$count2 = count($array2);
for ($key2=0; $key2<$count2; $key2++) {
  for ($key=0; $key<$count; $key++) if ($array[$key] == $array2[$key2]) {}
}
echo number_format(microtime(true)-$start, 2)."s\n";

$array = array();
for ($i=0; $i<1000000; $i++) $array[] = $i*2;

// test7: foreach big-array
$start = microtime(true);
foreach ($array as &$val) $val++;
echo number_format(microtime(true)-$start, 2)."s\n";

// test8: for big-array
$start = microtime(true);
for ($key=0; $key<count($array); $key++) $array[$key]++;
echo number_format(microtime(true)-$start, 2)."s\n";

// test8b: for big-array, doing count() outside the loop!
$start = microtime(true);
$count = count($array);
for ($key=0; $key<$count; $key++) $array[$key]++;
echo number_format(microtime(true)-$start, 2)."s\n";
}
test();

Here are some results from PHP 5.4.4 and HHVM (2014-05-04, QEMU 2.3 GHz, 64bit):
php hhvm
2.78s0.47s
2.90s0.44s
2.97s0.44s
6.90s1.36s
6.27s1.33s
5.83s1.13s
6.24s1.15s
0.07s0.04s
0.24s0.04s
0.11s0.03s
Using HHVM instead of PHP gives big improvements.

With Javascript (node.js) you'll get similar values:
example.js (run: node example.js)
var array = [];
for (i=0; i<50000; i++) array.push(i*2);

var array2 = [];
for (i=20000; i<21000; i++) array2.push(i*2);

// js-test1: for big-array (for small array)
var start = new Date().getTime();
var length = array.length;
var length2 = array2.length;
for (key=0; key<length; key++) {
  for (key2=0; key2<length2; key2++) if (array[key] == array2[key2]) {}
}
console.log((new Date().getTime() - start) / 1000); // 1.53s

// js-test2: foreach big-array (foreach small array)
start = new Date().getTime();
for (key in array) {
  for (key2 in array2) if (array[key] == array2[key2]) {}
}
console.log((new Date().getTime() - start) / 1000); // 6.32s

var array3 = [];
for (i=0; i<1000000; i++) array3.push(i*2);

// js-test3: for big-array
start = new Date().getTime();
length3 = array3.length;
for (key=0; key<length3; key++) array3[key]++;
console.log((new Date().getTime() - start) / 1000); // 0.03s
tested with QEMU 2.3 GHz, node.js v0.10

With C++ (gcc 4.6 win32) you'll also get similar values:
example.cpp (run: g++ -o example example.cpp && ./example)
#include <sys/time.h>
#include <stdio.h>
#include <vector>
using namespace std;

main() {
  struct timeval start, end;

  vector<int> array;
  for(int i=0; i < 50000; i++) array.push_back(i*2);

  vector<int> array2;
  for(int i=20000; i < 21000; i++) array2.push_back(i*2);

  gettimeofday(&start, NULL);
  int array_size = array.size();
  int array2_size = array2.size();
  for (int key=0; key<array_size; key++)
    for (int key2=0; key2<array2_size; key2++)
      if (array[key] == array2[key2]) {}

  gettimeofday(&end, NULL);
  printf("%lf\n", (float)(end.tv_sec - start.tv_sec +
    (end.tv_usec - start.tv_usec)/1000000.0)); // 0.61s, 0.00s (-O3)

  vector<int> array3;
  for(int i=0; i < 1000000; i++) array3.push_back(i*2);

  gettimeofday(&start, NULL);
  int array3_size = array3.size();
  for(int i=0; i < array3_size; i++) array3[i]++;
  gettimeofday(&end, NULL);
  printf("%lf\n", (float)(end.tv_sec - start.tv_sec +
    (end.tv_usec - start.tv_usec)/1000000.0)); // 0.009s, 0.001s (-O3)
}
tested with QEMU 2.3 GHz, gcc 4.7

And Java (Java 1.7 win64):

example.java (run: javac -g:none example.java && java example)
import java.util.ArrayList;
import java.util.Vector;

public class example {
  public static void main(String[] args) throws Exception {

    ArrayList<Integer> array = new ArrayList<Integer>();
    for (int i = 0; i < 50000; i++) array.add(i*2);

    ArrayList<Integer> array2 = new ArrayList<Integer>();
    for (int i = 20000; i < 21000; i++) array2.add(i*2);

    long start = System.currentTimeMillis();
    int array_size = array.size();
    int array2_size = array2.size();
    for (int key = 0; key < array_size; key++)
      for (int key2 = 0; key2 < array2_size; key2++)
        if (array.get(key).equals(array2.get(key2))) {}
    System.out.println((float) (System.currentTimeMillis() - start) / 1000);
    // 0.066s

    Vector<Integer> varray = new Vector<Integer>();
    for (int i = 0; i < 50000; i++) varray.add(i*2);

    Vector<Integer> varray2 = new Vector<Integer>();
    for (int i = 20000; i < 21000; i++) varray2.add(i*2);

    start = System.currentTimeMillis();
    int varray_size = varray.size();
    int varray2_size = varray2.size();
    for (int key = 0; key < varray_size; key++)
      for (int key2 = 0; key2 < varray2_size; key2++)
        if (varray.get(key).equals(varray2.get(key2))) {}
    System.out.println((float) (System.currentTimeMillis() - start) / 1000);
    // 1.652s

    ArrayList<Integer> array3 = new ArrayList<Integer>();
    for (int i = 0; i < 1000000; i++) array3.add(i*2);

    start = System.currentTimeMillis();
    int array3_size = array3.size();
    for (int i = 0; i < array3_size; i++) array3.set(i, array3.get(i)+1);
    System.out.println((float)(System.currentTimeMillis() - start) / 1000);
    // 0.164s

    Vector<Integer> varray3 = new Vector<Integer>();
    for (int i = 0; i < 1000000; i++) varray3.add(i*2);

    start = System.currentTimeMillis();
    int varray3_size = varray3.size();
    for (int i = 0; i < varray3_size; i++) varray3.set(i, varray3.get(i)+1);
    System.out.println((float)(System.currentTimeMillis() - start) / 1000);
    // 0.074s
  }
}
tested with QEMU 2.3 GHz, OpenJDK 1.6, 64bit

Go (1.4.2):

example.go (run: go build example.go && ./example)
package main

import "fmt"
import "time"

func main() {
    var array [50000]int
    for i := 0; i < 50000; i++ { array[i] = i*2 }

    var array2 [1000]int
    for i := 20000; i < 21000; i++ { array2[i-20000] = i*2 }

    t := time.Now()
    length := len(array)
    length2 := len(array2)
    for key := 0; key < length; key++ {
      for key2 := 0; key2 < length2; key2++ {
        if (array[key] == array2[key2]) {}
      }
    }
    fmt.Println(time.Now().Sub(t)) // 157.855682ms

    var array3 [1000000]int
    for i := 0; i < 1000000; i++ { array3[i] = i*2 }

    t2 := time.Now()
    length3 := len(array3)
    for key := 0; key < length3; key++ { array3[key]++ }
    fmt.Println(time.Now().Sub(t2)) // 4.363528ms
}

Zephir (0.7.1b):

example.zep (run: zephir init utils && vi utils/example.zep && zephir build && php -d extension=utils.so -r 'echo Utils\Example::run();')
namespace Utils;

class Example {

public static function run() {
    int i, i2;
    array array1 = [];
    let i = 0;
    while (i < 50000) {
        let array1[] = i*2;
        let i++;
    }

    array array2 = [];
    let i = 20000;
    while (i < 21000) {
        let array2[] = i*2;
        let i++;
    }

    var start;
    let start = microtime(true);
    int length, length2;
    let length = count(array1);
    let length2 = count(array2);
    let i = 0, i2 = 0;
    while (i < length) {
        while (i2 < length2) {
            if (array1[i] == array2[i2]) {}
            let i2++;
        }
        let i++;
    }    
    echo (microtime(true) - start) . PHP_EOL;
    
    array array3 = [];
    let i = 0;
    while (i < 1000000) {
        let array3[] = i*2;
        let i++;
    }

    let start = microtime(true);
    int length3;
    let length3 = count(array3);
    let i = 0;
    while (i < length3) {
        let array3[i] += 1;
        let i++;
    }
    echo (microtime(true) - start) . PHP_EOL;
}
}

New results (AMD Opteron 6128 2GHz virtualized):
php
5.5.9

4.71
4.77
6.43
9.20
10.81
8.76
11.09
0.15
0.37
0.20
php 7.0
2015-8-1

1.34
1.69
1.39
3.79
3.36
3.51
3.69
0.11
0.12
0.08
hhvm
3.8.1

0.65
0.61
0.68
1.34
1.44
1.06
1.14
0.09
0.07
0.05
node.js
0.10.25

1.953
9.218





0.051
c++
gcc 4.8.4

0.91601






0.01149
c++ -O3
gcc 4.8.4

0.00000






0.00101
go
1.4.2

0.15866






0.00427
OpenJDK
1.7.0_79

0.114
3.732





0.124
0.253
Zephir
0.7.1b

0.0001






0.1510

Note:
int len = array.size(); for (int key=0; key < len; key++)
instead of
for (int key=0; key < array.size(); key++)
makes the code 30 percent faster!

5 comments:

  1. foreach() is mostly slower than for().

    Your foreach() tests use variables that are not actually passed from another scope (which is the reality in 90% of the cases) AND your for() tests execute count() on each iteration, which is just bad usage of the loop. Here's a proper one:

    for ($i = 0, $c = count($array); $i < $c; $i++) {}

    Plus, you can't compare PHP to C++ - they just operate on different levels.

    ReplyDelete
  2. Thanks for the hint, I've put the count() outside the loop. I'll re-run the hiphop tests in the next days...

    ReplyDelete
  3. I was reading today an article where the author came to a different conclusion, stating that "foreach loop" is slower than "for loop":

    http://sheriframadan.com/2012/10/a-closer-look-into-php-arrays/

    How could we reconcile these two different views?

    ReplyDelete
    Replies
    1. I made a new test with new hardware, see:
      https://gist.github.com/4376836
      You might also run a test on your own hardware to check the results.

      Delete
  4. First of all thank you for sharing this informative blog.. This blog having more useful information that information explanation are step by step and very clear so easy and interesting to read.. After reading this blog i am strong in this topic which helpful to cracking the interview easily..

    php training course syllabus | php training topics

    ReplyDelete

Labels

performance (23) benchmark (6) MySQL (5) architecture (5) coding style (5) memory usage (5) HHVM (4) C++ (3) Java (3) Javascript (3) MVC (3) SQL (3) abstraction layer (3) framework (3) maintenance (3) Go (2) Golang (2) HTML5 (2) ORM (2) PDF (2) Slim (2) Symfony (2) Zend Framework (2) Zephir (2) firewall (2) log files (2) loops (2) quality (2) real-time (2) scrum (2) streaming (2) AOP (1) Apache (1) Arrays (1) C (1) DDoS (1) Deployment (1) DoS (1) Dropbox (1) HTML to PDF (1) HipHop (1) OCR (1) OOP (1) Objects (1) PDO (1) PHP extension (1) PhantomJS (1) SPL (1) SQLite (1) Server-Sent Events (1) Silex (1) Smarty (1) SplFixedArray (1) Unicode (1) V8 (1) analytics (1) annotations (1) apc (1) archiving (1) autoloading (1) awk (1) caching (1) code quality (1) column store (1) common mistakes (1) configuration (1) controller (1) decisions (1) design patterns (1) disk space (1) dynamic routing (1) file cache (1) garbage collector (1) good developer (1) html2pdf (1) internationalization (1) invoice (1) just-in-time compiler (1) kiss (1) knockd (1) legacy code (1) legacy systems (1) logtop (1) memcache (1) memcached (1) micro framework (1) ncat (1) node.js (1) openssh (1) pfff (1) php7 (1) phpng (1) procedure models (1) ramdisk (1) recursion (1) refactoring (1) references (1) regular expressions (1) search (1) security (1) sgrep (1) shm (1) sorting (1) spatch (1) ssh (1) strange behavior (1) swig (1) template engine (1) threads (1) translation (1) ubuntu (1) ufw (1) web server (1) whois (1)