Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Recursion with PHP 7, HHVM 3.8, Javascript, Java and C/C++ (Update: Go, Zephir)

Lessons learned:
  • HHVM runs recursive function calls 20 times faster than PHP
  • HHVM runs recursive function calls as fast as Javascript
  • HHVM runs recursive function calls 3 times slower than Java
  • HHVM runs recursive function calls 5 times slower than C
  • HHVM runs recursive function calls 2 times slower than Go

Here is a sample script using the Ackermann function:

<?php

function ack($n, $m) {
if ($n == 0) return $m + 1;
else if ($m == 0) return ack($n - 1, 1);
else return ack($n - 1, ack($n, $m - 1));
}

function ack_while($n, $m) {
while ($n != 0) {
if ($m == 0) $m = 1;
else $m = ack_while($n, $m - 1);
$n--;
}
return $m + 1;
}

$start = microtime(true);
ack_while(3, 10);
echo number_format(microtime(true)-$start, 4).'s'.PHP_EOL;

$start = microtime(true);
ack(3, 10);
echo number_format(microtime(true)-$start, 4).'s'.PHP_EOL;

Here is a sample script using the Fibonacci function:

<?php

function fib_it($n) {
$a = 0;
$b = 1;
for ($i = 0; $i < $n; $i++){
$sum = $a+$b;
$a = $b;
$b = $sum;
}
return $a;
}

function fib_rec($n) {
if ($n < 3) return 1;
return fib_rec($n - 1) + fib_rec($n - 2);
}

$start = microtime(true);
fib_it(40);
echo number_format(microtime(true)-$start, 4).'s'.PHP_EOL;

$start = microtime(true);
fib_rec(40);
echo number_format(microtime(true)-$start, 4).'s'.PHP_EOL;

Results: (AMD Opteron 6128 3Ghz virtualized, 64bit)

fib(40) PHP 5.5.9:
0.0000s
45.0391s

fib(40) PHP 7.0.0:
0.0000s
19.4653s

fib(40) with HHVM 3.8.1:
0.0060s
1.7428s

fib(40) with Go 1.2.1:
0.0000017s
0.9577085s

fib(40) with Java OpenJDK 1.7:
0.0s
0.565s

fib(40) with C (gcc 4.7):
0.358s

fib(40) with Javascript (node.js 0.10.25):
0.007s
1.667s

fib(40) with Zephir (0.7.1b):
0.000s
did not finish.


ack(3,10) PHP 5.5.9:
14.5458s
16.1864s

ack(3,10) PHP 7.0.0:
3.5186s
6.0263s

ack(3,10) with HHVM 3.8.1:
Fatal error: Stack overflow in /ack.php on line 12

ack(3,10) with Go 1.2.1:
0.292307s
0.346981s

ack(3,10) with Java OpenJDK 1.7:
0.121
0.222

ack(3,10) with C (gcc 4.7):
0.090s

ack(3,10) with Javascript (node.js 0.10.25):
0.378s
0.657s

ack(3,10) with Zephir (0.7.1b):
PHP Fatal error: Maximum recursion depth exceeded in Command line code on line 1

ack.c (run: gcc -O3 -o ack.out ack.c && time ./ack.out)

unsigned int ack(unsigned int n, unsigned int m) {
if (n == 0) return m + 1;
else if (m == 0) return ack(n - 1, 1);
else return ack(n - 1, ack(n, m - 1));
}

unsigned int ack_while(unsigned int n, unsigned int m) {
while (n != 0) {
if (m == 0) {
m = 1;
} else {
m = ack_while(n, m - 1);
}
n--;
}
return m + 1;
}

int main(int argc, char* argv[]) {
ack_while(3, 10);
ack(3, 10);
}

fib.c (run: gcc -O3 -o fib.out fib.c && time ./fib.out)

unsigned int fib_it(unsigned int n) {
unsigned int a = 0;
unsigned int b = 1;
unsigned int sum;
unsigned int i;
for (i = 0; i < n; i++){
sum = a + b;
a = b;
b = sum;
}
return a;
}

unsigned int fib_rec(unsigned int n) {
if (n < 3) return 1;
return fib_rec(n - 1) + fib_rec(n - 2);
}

int main(int argc, char* argv[]) {
fib_it(40);
fib_rec(40);
}

ack.js (run: time nodejs ack.js)

function ack(n, m) {
if (n == 0) return m + 1;
else if (m == 0) return ack(n - 1, 1);
else return ack(n - 1, ack(n, m - 1));
}

function ack_while(n, m) {
while (n != 0) {
if (m == 0) {
m = 1;
} else {
m = ack_while(n, m - 1);
}
n--;
}
return m + 1;
}

var start = new Date().getTime();
ack_while(3, 10);
console.log((new Date().getTime() - start) / 1000 + 's');

start = new Date().getTime();
ack(3, 10);
console.log((new Date().getTime() - start) / 1000 + 's');

fib.js (run: time nodejs fib.js)

function fib_it(n) {
var a = 0;
var b = 1;
var sum = 0;
for (var i = 0; i < n; i++){
sum = a + b;
a = b;
b = sum;
}
return a;
}

function fib_rec(n) {
if (n < 3) return 1;
return fib_rec(n - 1) + fib_rec(n - 2);
}

var start = new Date().getTime();
fib_it(40);
console.log((new Date().getTime() - start) / 1000 + 's');

start = new Date().getTime();
fib_rec(40);
console.log((new Date().getTime() - start) / 1000 + 's');

ack.java (run: javac -g:none ack.java && java ack)

public class ack {
public static int ack(int n, int m) {
if (n == 0) return m + 1;
else if (m == 0) return ack(n - 1, 1);
else return ack(n - 1, ack(n, m - 1));
}

public static int ack_while(int n, int m) {
while (n != 0) {
if (m == 0) {
m = 1;
} else {
m = ack_while(n, m - 1);
}
n--;
}
return m + 1;
}

public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
ack_while(3, 10);
System.out.println((float) (System.currentTimeMillis() - start) / 1000);

start = System.currentTimeMillis();
ack(3, 10);
System.out.println((float)(System.currentTimeMillis() - start) / 1000);
}
}

fib.java (run: javac -g:none fib.java && java fib)

public class fib {
public static int fib_it(int n) {
int a = 0;
int b = 1;
int sum;
for (int i = 0; i < n; i++){
sum = a + b;
a = b;
b = sum;
}
return a;
}

public static int fib_rec(int n) {
if (n < 3) return 1;
return fib_rec(n - 1) + fib_rec(n - 2);
}

public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
fib_it(40);
System.out.println((float) (System.currentTimeMillis() - start) / 1000);

start = System.currentTimeMillis();
fib_rec(40);
System.out.println((float)(System.currentTimeMillis() - start) / 1000);
}
}

fib.go (run: go run fib.go)

package main

import "fmt"
import "time"

func main() {
t := time.Now()
fib_it(40)
fmt.Println(time.Now().Sub(t))

t = time.Now()
fib_rec(40)
fmt.Println(time.Now().Sub(t))
}

func fib_it(n int) int {
a := 0
b := 1
var sum int

for i := 0; i < n; i++ {
sum = a + b
a = b
b = sum
}
return a
}

func fib_rec(n int) int {
if n < 3 {
return 1
}
return fib_rec(n - 1) + fib_rec(n - 2)
}

ack.go (run: go run ack.go)

package main

import "fmt"
import "time"

func main() {
t := time.Now()
ack_while(3, 10)
fmt.Println(time.Now().Sub(t))

t = time.Now()
ack(3, 10)
fmt.Println(time.Now().Sub(t))
}

func ack_while(n int, m int) int {
for i := n; i > 0; i-- {
if m == 0 {
m = 1
} else {
m = ack_while(i, m - 1)
}
}
return m + 1
}

func ack(n int, m int) int {
if n == 0 {
return m + 1
} else if m == 0 {
return ack(n - 1, 1)
}
return ack(n - 1, ack(n, m - 1));
}

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() {
var start;
let start = microtime(true);
self::ack(3, 8);
echo (microtime(true) - start) . PHP_EOL;

let start = microtime(true);
self::ack_while(3, 8);
echo (microtime(true) - start) . PHP_EOL;
}

public static function ack(int n, int m) {
if (n == 0) {
return m + 1;
} elseif (m == 0) {
return self::ack(n - 1, 1);
} else {
return self::ack(n - 1, self::ack(n, m - 1));
}
}

public static function ack_while(int n, var m) {
while (n != 0) {
if (m == 0) {
let m = 1;
} else {
let m = self::ack_while(n, m - 1);
}
let n--;
}
return m + 1;
}
}

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() {
var start;
let start = microtime(true);
self::fib_it(40);
echo (microtime(true) - start) . PHP_EOL;

let start = microtime(true);
self::fib_rec(40);
echo (microtime(true) - start) . PHP_EOL;
}

public static function fib_it(int n) {
int a = 0;
int b = 1;
int sum;

int i = 0;
while (i < n) {
let sum = a + b;
let a = b;
let b = sum;
let i++;
}
return a;
}

public static function fib_rec(int n) {
if (n < 3) {
return 1;
}
return self::fib_rec(n - 1) + self::fib_rec(n - 2);
}
}

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!

How to implement a real-time chat server in PHP using Server-Sent Events (update: added C benchmark)

Lessons learned:
  • A web server written in PHP can give more than 10000 req/s on small hardware
  • A web server written in PHP is not slower than being written in Java (without threads)
  • A web server written in PHP is 30 percent slower than being written in C (without threads)
  • Realtime applications can be developed in PHP without problems

PHP normally runs inside a web server like Apache or nginx. This keeps all requests separate from each other and does not allow sharing memory or connections. To implement a chat server, the browser has to poll the server regularly for new data. The data is stored in a database and looked up for each request. This is very slow, takes a lot of resources on the server and does not give messages in realtime.
Newer browsers support data being pushed from the server to the client. There are two techniques used: WebSockets (full-duplex) and Server-Sent Events (push notifications)
Using these techniques, one connection stays open for each client and the server sends (pushes) messages whenever new data is available. To handle many connections – that stay open for a long time – and share messages between these connections, we run PHP as a standalone process on the shell and implement a web server directly in PHP.
In the browser, we use Server-Sent Events to receive messages in realtime and AJAX requests to send new messages. We allow more than one connection (=browser tab) per user. We allow sending messages to all users or one specific user. If the user is offline, the message will be kept in memory for later delivery.

Here is the code:

// Usage: run "php server.php" and open a few browser tabs with "http://<ip>:8000/".

// server.php #1 html page
$html = "<html>
<body>
<script>
var inactive = 0;
// show 'username (x)' in document title when there are
// new messages and the tab is not visible
window.onfocus = function(){
inactive = 0;
document.title = document.forms[0].nick.value;
};
window.onblur = function(){ inactive = 1; };
var html = function(s){ return s.replace(/>/g,'>').replace(/</g,'<'); };
</script>

<!-- nickname form, start receiving messages on submit -->
<form onsubmit=\"
var source = new EventSource('/'+this.nick.value);
source.onmessage = function(event){
document.getElementById('content').innerHTML += html(event.data)+'<br>';
if (inactive) document.title = document.forms[0].nick.value+' ('+(inactive++)+')';
};
this.style.display = 'none';
document.forms[1].style.display = '';
document.forms[1].msg.focus();
document.title = this.nick.value;
return false;
\">
<input type='text' name='nick' required='true' autofocus='true' />
<input type='submit' value='Choose nickname'/>
</form>

<!-- message form, send new message as ajax request -->
<form onsubmit=\"
var ajax = new XMLHttpRequest();
ajax.open('GET', 'http://localhost:8000/msg/' + escape(document.forms[0].nick.value) +
'/'+escape(this.msg.value));
ajax.send();
this.msg.value = this.msg.value.substr(0, this.msg.value.indexOf(' ')+1);
this.msg.focus();
return false;
\" style='display:none;'>
<input type='text' name='msg' required='true' placeholder='nickname message' style='width:250px;' />
<input type='submit' value='Send message' />
<input type='button' value='Clear chat' onclick=\"document.getElementById('content').innerHTML='';\" />
</form>

<div id='content'><!-- chat messages --></div>
</body>";

// server.php #2 socket server (port 8000)
$socket = stream_socket_server("tcp://0.0.0.0:8000", $errno, $err) or die($err);
$conns = array($socket);
$conn_ids = array(0);
$conn_user = array();
$msgs = array();

// server loop
while (true) {
$reads = $conns;
// get number of connections with new data
$mod = stream_select($reads, $write, $except, 5);
if ($mod===false) break;

foreach ($reads as $read) {
if ($read===$socket) {
$conn = stream_socket_accept($socket);
$recv = fread($conn, 1024);
if (empty($recv)) continue;

if (strpos($recv, "GET / ")===0) {
// serve static html page from memory
fwrite($conn, "HTTP/1.1 200 OK\r\n". "Connection: close\r\n".
"Content-Type: text/html; charset=UTF-8\r\n\r\n");
fwrite($conn, $html);
stream_socket_shutdown($conn, STREAM_SHUT_RDWR);

} else if (strpos($recv, "GET /msg/")===0) {
// ajax request: send a message
// syntax: GET /msg/user_from/user_to%20message
// e.g. GET /msg/john/mary%20hello
stream_socket_shutdown($conn, STREAM_SHUT_RDWR);
preg_match("!GET /msg/([^/]+)/(\S+)!", $recv, $match);
$user = $match[1];
$match[2] = urldecode($match[2]);
if (!strpos($match[2], " ")) continue;
list($target, $msg) = explode(" ", $match[2], 2);

if ($target=="all") {
// send message to all users
foreach ($conns as $i=>$conn) {
if ($i!=0) fwrite($conn, "data: ".$user." to all: ".$msg."\n\n");
}

} else if (isset($conn_user[$target])) {
// send message to one user and to the originator
if ($target!=$user) foreach ($conn_user[$target] as $conn) {
fwrite($conn, "data: ".$user.": ".$msg."\n\n");
}
if (isset($conn_user[$user])) foreach ($conn_user[$user] as $conn) {
fwrite($conn, "data: You to ".$target.": ".$msg."\n\n");
}

} else {
// user is offline, keep message in memory for later delivery
if (!isset($msgs[$target])) $msgs[$target] = "";
$msgs[$target] .= "data: ".$user." (".@date("Y-m-d H:i")."): ".$msg."\n\n";
foreach ($conn_user[$user] as $conn) {
fwrite($conn, "data: You to ".$target." (offline): ".$msg."\n\n");
}
}

} else if (strpos($recv, "text/event-stream")===false) {
// block other requests like favicon.ico
stream_socket_shutdown($conn, STREAM_SHUT_RDWR);

} else {
// login as new user
// syntax: GET /username e.g. GET /john
preg_match("!GET /(\S+)!", $recv, $match);
if (!isset($match[1])) continue;
$user = $match[1];
echo "connect ".$user." from ".stream_socket_get_name($conn, true)."\n";

fwrite($conn, "HTTP/1.1 200 OK\r\n". "Connection: close\r\n".
"Content-Type: text/event-stream\r\n\r\n");
fwrite($conn, "data: Welcome ".$user."!\n\n");
fwrite($conn, "data: now online: ".implode(", ", array_keys($conn_user))."\n\n");

// deliver messages sent when user was offline
if (isset($msgs[$user])) {
fwrite($conn, $msgs[$user]);
unset($msgs[$user]);
}
// notify other users
foreach ($conns as $i=>$c) {
if ($i!=0) fwrite($c, "data: ".$user." has joined.\n\n");
}
// register connection in pool
$conns[] = $conn;
$conn_ids[] = $user;
// allow multiple connections for 1 user
$conn_user[$user][] = $conn;
}
} else {
$data = fread($read, 1024);
if ($data=="" or $data===false) {
// user/browser closed connection
if ($data!==false) stream_socket_shutdown($read, STREAM_SHUT_RDWR);
$conn_id = array_search($read, $conns, true);
unset($conns[$conn_id]);

// unregister connection for user
$user = $conn_ids[$conn_id];
unset($conn_ids[$conn_id]);
$conn_id = array_search($read, $conn_user[$user], true);
unset($conn_user[$user][$conn_id]);

if (empty($conn_user[$user])) {
unset($conn_user[$user]);
// notify other users
foreach ($conns as $i=>$c) {
if ($i!=0) fwrite($c, "data: ".$user." has left.\n\n");
} } } } } }
Please note that this implementation does not cover user authentication or saving messages to disk or database. Also, connection errors or timeouts are not handled on the browser side.
This code is just for demonstration of the concepts and the performance, so it is not OOP and it should not be used in production. Also it does not implement a complete HTTP stack or handle mime types.

To test the performance of our chat server, let's do some "GET / HTTP/1.1" and serve the static HTML output. ab works good here because it covers all possible scenarios.

php server.php (PHP 5.3.10, 3.4 GHz single core QEMU, VQ7 from Hetzner)

# serve static content
ab -n 10000 -c 10 http://localhost:8000/
Requests per second: 11601.30 [#/sec] (mean)
(memory usage is about 12.5M)

# receive messages (1st shell)
curl -sH ": text/event-stream" http://localhost:8000/john >/dev/null
# send messages (2nd shell)
ab -n 10000 -c 10 http://localhost:8000/msg/mary/john%20hello
Requests per second: 9872.04 [#/sec] (mean)

javac Server.java && java Server (IcedTea7 2.3.3, 1.7.0_09)

ab -n 10000 -c 10 http://localhost:8000/
Requests per second: 10440.45 [#/sec] (mean)

// Server.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server {
private static String readFile(String pathname) throws IOException {
File file = new File(pathname);
StringBuilder fileContents = new StringBuilder((int) file.length());
Scanner scanner = new Scanner(file);
try {
while (scanner.hasNextLine())
fileContents.append(scanner.nextLine() + "\n");
return fileContents.toString();
} finally {
scanner.close();
}
}

public static void main(String[] args) throws Exception {
final String html = readFile("test.html");
try (ServerSocket socket = new ServerSocket(8000)) {
while (true) {
final Socket client = socket.accept();
try (Socket c = client) {
while (true) {
DataInputStream dis = new DataInputStream(c.getInputStream());
DataOutputStream dos = new DataOutputStream(c.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(dis));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(dos));
String recv = in.readLine();
if (recv != null && recv.indexOf("GET / ") == 0)
out.write("HTTP/1.1 200 OK\r\nConnection: close\r\n"
+ "Content-Type: text/html; charset=UTF-8\r\n\r\n" + html);
// using StringBuilder was slower ...
out.close();
in.close();
}
} catch (IOException e) {
// ok
}
}
} catch (IOException e) {
System.out.println(e);
} } }

gcc -o server.bin server.c && ./server.bin (gcc 4.6.3)

ab -n 10000 -c 10 http://localhost:8000/
Requests per second: 15254.44 [#/sec] (mean)
(memory usage is about 0.5M)

// server.c
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
int server, instance;
socklen_t clilen;
char buffer[256];
struct sockaddr_in srv, cli;
char html[] = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/html;\
charset=UTF-8\r\n\r\n\
<html>\
<body>\
<script>\
var inactive = 0;\
...
</body>";
int html_len = strlen(html);

server = socket(AF_INET, SOCK_STREAM, 0);
bzero((char *) &srv, sizeof(srv));
srv.sin_family = AF_INET;
srv.sin_addr.s_addr = INADDR_ANY;
srv.sin_port = htons(8000);
int opt = 1;
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (bind(server, (struct sockaddr *) &srv, sizeof(srv)) < 0) {
perror("ERROR: bind");
exit(1);
}
listen(server,5);
clilen = sizeof(cli);
do {
instance = accept(server, (struct sockaddr *) &cli, &clilen);
if (instance < 0) continue;
bzero(buffer,256);
read(instance,buffer,256);
if (strstr(buffer, "GET / ")) write(instance, html, html_len);
close(instance);
} while(1);
return 0;
}

Apache (v2.2.22, from disk with logging enabled)

ab -n 10000 -c 10 http://localhost/public/test.html
Requests per second: 6491.84 [#/sec] (mean)

node test.js (node.js v0.6.12)

ab -n 10000 -c 10 http://localhost:8000/
Requests per second: 5554.03 [#/sec] (mean)

// test.js
var str = "<html>\
<body>\
<script>\
var inactive = 0;\
...
</body>";
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(str);
}).listen(8000, '127.0.0.1');
node test2.js (node.js v0.6.12)

ab -n 10000 -c 10 http://localhost:8000/
Requests per second: 9075.90 [#/sec] (mean)

// test2.js
var str = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n\
<html>\
<body>\
<script>\
var inactive = 0;\
...
</body>";
var net = require('net');
var server = net.createServer();
server.listen(8000, '127.0.0.1');
server.on('connection', function(sock) {
sock.end(str);
});