Comparison script languages for the fractal geometry

24
ago/09
18

This article will compare the latest incarnations of Ruby, with the latest in Python, Groovy, PHP, Lua, Perl and Java too, to have a comparison with a pre-compiled language. We will see, how scripting languages behave if applied to fractal geometry, more precisely an family Mandelbrot algorithm.
Browsing on the net, I found a comparison very interesting but a bit dated, dates back more than two years ago. Since then things have changed and I took advantage to make an update, not including all of those languages but only for more known. This is an opportunity to compare Ruby and Python versions even in their Java and. NET, an intention that I had since long time.

Using a fractal as a convenient benchmark plus: if an attempt to optimize not be successful if it has been the evidence and being drawn in real time, you can feel the speed. The fractal is drawn in ASCII also because the use of external libraries would have drugged the outcome.

                                       *
                                       *
                                       *
                                       *
                                       *
                                      ***
                                     *****
                                     *****
                                      ***
                                       *
                                   *********
                                 *************
                                ***************
                             *********************
                             *********************
                              *******************
                              *******************
                              *******************
                              *******************
                            ***********************
                              *******************
                              *******************
                             *********************
                              *******************
                              *******************
                               *****************
                                ***************
                                 *************
                                   *********
                                       *
                                ***************
                            ***********************
                         * ************************* *
                         *****************************
                      * ******************************* *
                       *********************************
                      ***********************************
                    ***************************************
               *** ***************************************** ***
               *************************************************
                ***********************************************
                 *********************************************
                 *********************************************
                ***********************************************
                ***********************************************
              ***************************************************
               *************************************************
               *************************************************
              ***************************************************
              ***************************************************
         *    ***************************************************    *
       *****  ***************************************************  *****
       ****** *************************************************** ******
      ******* *************************************************** *******
    ***********************************************************************
    ********* *************************************************** *********
       ****** *************************************************** ******
       *****  ***************************************************  *****
              ***************************************************
              ***************************************************
              ***************************************************
              ***************************************************
               *************************************************
               *************************************************
              ***************************************************
                ***********************************************
                ***********************************************
                  *******************************************
                   *****************************************
                 *********************************************
                **** ****************** ****************** ****
                 ***  ****************   ****************  ***
                  *    **************     **************    *
                         ***********       ***********
                         **  *****           *****  **
                          *   *                 *   *

This is the system for the test:
Dell Inspiron 9400, Centrino Duo, T7200 @ 2Ghz 4Mb Cache L1, Ram 2Gb @ 667Mhz
Windows XP pro SP3
Java 6 update 15
Microsoft .NET 3.5 SP1

These are the performance results obtained from an average of five runs, took after have executed some void attempts (I have not trusted the VM startup):

Language      Time (in seconds)  n times slower tha java
_____________________________________________________________
Java 6 update 15    0,153
Lua 5.1.4           0,815	           5x
Php 5.3.0           2,083	          14x
Python 2.6.2        2,269 	          15x
Python 3.1.1        1,566 	          10x
Jython 2.5.0        2,850 	          19x
Jruby 1.3.1         2,466 	          16x
Groovy 1.6.3        6,491 	          42x
Ruby 1.9.1 p129	    2,688 	          18x
Ruby 1.8.6 p368	    6,863 	          45x
Ruby 1.8.6 p111	    9,709 	          63x
IronRuby 0.9.0	    6,038 	          39x
IronPyhon 2.0.2     0,978 	           6x
Perl 5.10.0         2,722 	          18x

This is the chart, of course lower values indicate better performance

Chart

These are the scripts used to generate the fractal, the originals was good, I have only done a few simple changes to run Python 3.1 or slightly improve the already excellent readability in Ruby and Lua.

Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// by Erik Wrenholt
import java.util.*;
 
class Bench1
{  
	static int BAILOUT = 16;
	static int MAX_ITERATIONS = 1000;
 
	private static int iterate(float x, float y)
	{
		float cr = y-0.5f;
		float ci = x;
		float zi = 0.0f;
		float zr = 0.0f;
		int i = 0;
		while (true) {
			i++;
			float temp = zr * zi;
			float zr2 = zr * zr;
			float zi2 = zi * zi;
			zr = zr2 - zi2 + cr;
			zi = temp + temp + ci;
			if (zi2 + zr2 > BAILOUT)
				return i;
			if (i > MAX_ITERATIONS)
				return 0;
		}
	}
 
	public static void main(String args[])
	{
		Date d1 = new Date();
		int x,y;
		for (y = -39; y < 39; y++) {
			System.out.print("n");
			for (x = -39; x < 39; x++) {
				if (iterate(x/40.0f,y/40.0f) == 0) 
					System.out.print("*");
				else
					System.out.print(" ");
 
			}
		}
		Date d2 = new Date();
		long diff = d2.getTime() - d1.getTime();
		System.out.println("nJava Elapsed " + diff/1000.0f);
 
	}
}

Lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
-- By Erik Wrenholt
 
local BAILOUT = 16
local MAX_ITERATIONS = 1000
 
function iterate(x,y)
 
  local cr = y-0.5
  local ci = x
  local zi = 0.0
  local zr = 0.0
  local i = 0
 
  while 1 do
    i = i+1
    local temp = zr * zi
    local zr2 = zr*zr
    local zi2 = zi*zi
    zr = zr2-zi2+cr
    zi = temp+temp+ci
    if (zi2+zr2 > BAILOUT) then
      return i
    end
    if (i > MAX_ITERATIONS) then
      return 0
    end
  end
end
 
function bench1()
  local t = os.clock()
  for y = -39, 38 do
    for x = -39, 38 do
    if (iterate(x/40.0, y/40) == 0) then io.write("*") else io.write(" ") end
    end
    io.write("n")
  end
  io.write(string.format("Time Elapsed %.3fn", os.clock() - t))
end
 
bench1()

Php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
define("BAILOUT",16);
define("MAX_ITERATIONS",1000);
 
class Bench1
{
 
	function Bench1()
	{
		$d1 = microtime(1);
		for ($y = -39; $y < 39; $y++) {
			echo("n");
			for ($x = -39; $x < 39; $x++) {
				if ($this->iterate($x/40.0,$y/40.0) == 0) 
					echo("*");
				else
					echo(" ");
			}
		}
		$d2 = microtime(1);
		$diff = $d2 - $d1;
		printf("nPHP Elapsed %0.3f", $diff);
	}
 
	function iterate($x,$y)
	{
		$cr = $y-0.5;
		$ci = $x;
		$zi = 0.0;
		$zr = 0.0;
		$i = 0;
		while (true) {
			$i++;
			$temp = $zr * $zi;
			$zr2 = $zr * $zr;
			$zi2 = $zi * $zi;
			$zr = $zr2 - $zi2 + $cr;
			$zi = $temp + $temp + $ci;
			if ($zi2 + $zr2 > BAILOUT)
				return $i;
			if ($i > MAX_ITERATIONS)
				return 0;
		}
	}
}
 
new Bench1();
?>

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import sys, time
stdout = sys.stdout
 
BAILOUT = 16
MAX_ITERATIONS = 1000
 
class Bench1:
  def __init__(self):
    print ('Rendering...')
    for y in range(-39, 39):
      stdout.write('n')
      for x in range(-39, 39):
        i = self.start(x/40.0, y/40.0)
 
        if i == 0:
          stdout.write('*')
        else:
          stdout.write(' ')
 
  def start(self, x, y):
    cr = y - 0.5
    ci = x
    zi = zr = 0.0
    i = 0
 
    while True:
      i += 1
      temp = zr * zi
      zr2 = zr * zr
      zi2 = zi * zi
      zr = zr2 - zi2 + cr
      zi = temp + temp + ci
 
      if zi2 + zr2 > BAILOUT:
        return i
      if i > MAX_ITERATIONS:
        return 0
 
t = time.time()
Bench1()
print ('nPython Elapsed %.3f' % (time.time() - t))

Groovy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//Created By Marco Mastrodonato 22/09/2009
 
class Bench1{
    public int BAILOUT = 16
    public int MAX_ITERATIONS = 1000
 
    def Bench1(){
        println("Rendering...")
        for (y in -39..39){
            println("")
            for (x in -39..39){
                if (iterate(x/40.0, y/40.0) == 0){
                    print("*")
                } else {
                    print(" ")
                }
            }
        }
    }
 
    def iterate(x,y){
        float cr = y-0.5
        float ci = x
        float zi = 0.0
        float zr = 0.0
        def i = 0
        while(1){
            i += 1
            float temp = zr * zi
            float zr2 = zr * zr
            float zi2 = zi * zi
            zr = zr2 - zi2 + cr
            zi = temp + temp + ci
            if (zi2 + zr2 > BAILOUT){ 
                return i
            }
            if (i > MAX_ITERATIONS){ 
                return 0
            } 
        }
    }
 
}
 
time1 = new Date().time
new Bench1()
time2 = new Date().time
float elapsed = (time2 - time1)/1000
println("nGroovy Elapsed ${elapsed}")

Ruby

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
BAILOUT = 16
MAX_ITERATIONS = 1000
 
class Bench1
 
  def initialize
    puts "Rendering..."
    for y in -39..39
      print "n"
      for x in -39..39
        i = iterate x/40.0, y/40.0
        if i == 0 then print "*" else print " " end
      end
    end
  end
 
  def iterate(x,y)
    cr = y-0.5
    ci = x
    zi = zr = 0.0
    i = 0
    while true
      i += 1
      temp = zr * zi
      zr2 = zr * zr
      zi2 = zi * zi
      zr = zr2 - zi2 + cr
      zi = temp + temp + ci
      return i if zi2 + zr2 > BAILOUT
      return 0 if i > MAX_ITERATIONS
    end
  end
end
 
time = Time.now
Bench1.new
puts "nRuby Elapsed %.3f" % (Time.now - time)

Perl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# Ported from C to Perl by Anders Bergh <anders1@gmail.com>
 
$BAILOUT=16;
$MAX_ITERATIONS=1000;
 
$begin = time();
 
sub mandelbrot {
       local $x = $_[0];
       local $y = $_[1];
 
       local $cr = $y - 0.5;
       local $ci = $x;
       local $zi = 0.0;
       local $zr = 0.0;
       local $i = 0;
 
       while (1)
       {
               $i = $i + 1;
               local $temp = $zr * $zi;
               local $zr2 = $zr * $zr;
               local $zi2 = $zi * $zi;
               $zr = $zr2 - $zi2 + $cr;
               $zi = $temp + $temp + $ci;
               if ($zi2 + $zr2 > $BAILOUT)
               {
                       return $i;
               }
               if ($i > $MAX_ITERATIONS)
               {
                       return 0;
               }
       }
}
 
for ($y = -39; $y < 39; $y++)
{
       print("n");
       for ($x = -39; $x < 39; $x++)
       {
               $i = mandelbrot($x/40.0, $y/40.0);
               if ($i == 0)
               {
                       print("*");
               }
               else
               {
                       print(" ");
               }
       }
}
print("n");
 
$end = time() - $begin;
 
printf ("Perl Elapsed %.3fn",$end);

Comments:

The speed of Lua isn’t a news: only 5 times slower than compiled Java code, the best result. Its simplicity is its strength, maybe that’s what makes it so fast? It was adopted by Blizzard in the videogame “World of Warcraft” and if they did so, with no doubt, there is a reason. It is not object-oriented, does not natively support objects even if there is a project LOOP that extends this programming model.
PHP and Perl does not need comments.
Among the C version of Ruby and Python is clearly faster the second. The fair comparison would be the Rb1.8.6 with Py2.6.2 and Rb1.9.1 with Py3.1.1.
The challenge between the versions that uses the Java VM: Groovy, Jython and JRuby, sees the last as the winner. Groovy is far behind as performance in this test but the biggest question I have is: who is it for? As syntax is not bad but, imho, Ruby is even more fluent and then have that rake that is so convenient for many things.
The challenge between the .NET versions sees IronPython incredibly forward! But what they put in, the dynamite? It will be very interesting to examine the new ASP.NET MVC framework recently arrived at version 1 and that will be included in the framework. Net 4, there are projects for both IronRuby IronPython.
If this article was interesting, perhaps you can find something else through the advertisements of my sponsor, is in the right column, thanks!
Bye!

Commenti (18) Trackback (0)
  1. informatimago
    13:27 on 24 agosto 2009

    Oops, Please, add this line at the beginning of the bench1.lisp:

    (declaim (optimize (speed 3) (space 2) (debug 0) (safety 0)))

  2. informatimago
    13:28 on 24 agosto 2009

    For completeness, could you add Common Lisp to your test, please?

    You may test with SBCL (native-code compiler, MS-Windows port in progress):
    http://prdownloads.sourceforge.net/sbcl/sbcl-1.0.29-x86-windows-binary.msi

    —-(bench1.lisp)————————-
    (declaim (optimize (speed 3) (space 2) (debug 0) (safety 0)))

    (defparameter *bailout* (coerce 16.0 ’single-float))
    (defparameter *max-iterations* 1000)

    (defun bench1 ()
    (format t “Rendering…~%”) (force-output)
    (loop :for y :from -39 to 39 :do
    (terpri)
    (loop :for x :from -39 to 39 :do
    (princ (if (zerop (iterate (the single-float (/ x 40.0))
    (the single-float (/ y 40.0))))
    “*”
    ” “))))
    (finish-output))

    (defun iterate (x y)
    (declare (single-float x))
    (loop
    :with cr single-float = (- y 0.5)
    :with ci single-float = x
    :with zi single-float = 0.0
    :with zr single-float = 0.0
    :for i :from 0 :below *max-iterations*
    :do (let ((temp (* zr zi))
    (zr2 (* zr zr))
    (zi2 (* zi zi)))
    (declare (single-float temp zr2 zi2))
    (setf zr (+ (- zr2 zi2) cr)
    zi (+ temp temp ci))
    (when (< *bailout* (+ zi2 zr2))
    (return-from iterate i)))
    :finally (return-from iterate 0)))

    (time (bench1))
    ——————————————–

    Run the test with:

    sbcl –eval '(load (compile-file "bench1.lisp"))' –eval '(quit)'

    Thanks.

  3. Marco Mastrodonato
    15:16 on 24 agosto 2009

    Unfortunately, your comment has been catch by antispam, i will add lisp asap and thanks for your work

  4. Piyush
    19:55 on 25 agosto 2009

    Did you run jruby with –server command line switch ?

  5. lrz
    01:07 on 26 agosto 2009

    Too bad you’re testing this on Windows, because MacRuby gives 0.345s here (vs 1.930s for 1.9.2dev) :-)

  6. Dino Viehland
    04:49 on 26 agosto 2009

    On the Python implementation you should probably use xrange instead of range. Also “while 1:” instead of “while True:” will give CPython a perf improvement (but not IronPython until 2.6 – I’m not sure about Jython).

  7. Marco Mastrodonato
    22:20 on 26 agosto 2009

    @Piyush:
    No, thank you for letting me know this, it has slightly improved: 2.5 => 2.4

    @lrz:
    uhmmm… have you compiled by yourself? Which hardware do you have?

    @Dino Viehland:
    xrange improves perf on python 2.6 but there isn’t on 3.1, i prefer to have a unique script that works for every version. Using “while 1:” instead “True” gives me the same result

  8. Isaac Gouy
    03:14 on 27 agosto 2009

    > These are the performance results obtained from an average of five runs…

    Is there a reason that you haven’t sorted the results shown in the table or chart by “performance”?

    http://shootout.alioth.debian.org/u32q/benchmark.php?test=mandelbrot

  9. truncatei
    11:55 on 27 agosto 2009

    a better perl version:
    #!/usr/bin/perl
    # Ported from C to Perl by Anders Bergh

    $BAILOUT=16;
    $MAX_ITERATIONS=1000;

    $begin = time();

    sub mandelbrot {
    my $x = $_[0];
    my $y = $_[1];

    my $cr = $y – 0.5;
    my $ci = $x;
    my $zi = 0.0;
    my $zr = 0.0;
    my $i = 0;

    while (1)
    {
    $i = $i + 1;
    my $temp = $zr * $zi;
    my $zr2 = $zr * $zr;
    my $zi2 = $zi * $zi;
    $zr = $zr2 – $zi2 + $cr;
    $zi = $temp + $temp + $ci;
    if ($zi2 + $zr2 > $BAILOUT)
    {
    return $i;
    }
    if ($i > $MAX_ITERATIONS)
    {
    return 0;
    }
    }
    }

    my $str = “”;
    for ($y = -39; $y < 39; $y++)
    {
    $str .= "n"; #print("n");
    for ($x = -39; $x < 39; $x++)
    {
    $i = mandelbrot($x/40.0, $y/40.0);
    if ($i == 0)
    {
    $str .= "*"; #print("*");
    }
    else
    {
    $str .= " "; #print(" ");
    }
    }
    }
    $str .= "n"; #print("n");
    print $str;
    $end = time() – $begin;

    printf ("Perl Elapsed %.3fn",$end);

  10. Marco Mastrodonato
    14:15 on 27 agosto 2009

    @Isaac Gouy
    I tried to keep close similar platforms where it was possible: Python’s family, Ruby’s family, the two iron and I put groovy in the middle between jruby and jython for comparing JVM. I know that the comparison you linked is very complete but another point of view is not bad.

    @truncatei
    i’ll check it as soon as possible, thank you!

  11. Isaac Gouy
    17:37 on 27 agosto 2009

    > I tried to keep close similar platforms where it was possible

    Why? It’s arbitrary – why isn’t IronPython in the Python family?
    It just makes the data harder to read.

    > but another point of view is not bad

    http://www.timestretch.com/FractalBenchmark.html

    and

    http://theowoll.netau.net/benchmark.html

    and …

    Take “another point of view” from a /different/ view point – explore performance on a /different/ task.

  12. Marco Mastrodonato
    22:00 on 27 agosto 2009

    Isaac, I preferred to have IronPython close to IronRuby, which I wanted to put next to other versions. This is a blog where we talk mostly of Ruby, I slightly preferred it in the representation. When a photograph focuses on the subject, the background is blurry. This no mean it’s a bad photo. However, thanks for your suggestions.

  13. Marco Mastrodonato
    22:48 on 27 agosto 2009

    @truncatei
    Hi,
    I’ve taken a look to your code. Unfortunatelly, i can’t use it because is required to draw the fractal in real time.

  14. bc
    10:23 on 20 settembre 2009

    If you’re doing maths with python, you’d automatically use the numpy library and vectorise the calculation. Here’s a numpy’fied version:

    import numpy
    from numpy import logical_or

    class Bench2(object):
    def __init__(self):
    print (’Rendering…’)
    y,x = numpy.mgrid[-1.:1.:80j, -1.:1.:80j]
    i = self.start(x,y)
    out = numpy.ones(i.shape, dtype=numpy.byte) * 32
    out[i==0] = 42
    for row in out:
    stdout.write(row.tostring())
    stdout.write(’n')

    def start(self, x, y):
    “”"x & y are now arrays of shape (nx, ny)
    “”"
    c = (y – 0.5) + 1.j*x
    z = numpy.zeros(c.shape, numpy.complex128)
    mask = numpy.zeros(c.shape, numpy.bool)
    for i in xrange(MAX_ITERATIONS):
    z2 = z**2 + c
    mask = logical_or(mask, (z2.real + z2.imag) > BAILOUT, mask)
    z = z2
    return mask

    On my machine this runs 3.75x faster than the vanilla CPython version. In fact, even easier is to use Cython, add a few type-defs and get raw C-speed.

  15. bc
    14:31 on 20 settembre 2009

    OK, for an even more efficient numpy implementation try:

    class Bench2(object):
    ….def __init__(self):
    ……..print (’Rendering…’)
    ……..y,x = numpy.mgrid[-39/40.:39/40.:78j, -39/40.:39/40.:78j]
    ……..i = self.start(x,y)
    ……..out = numpy.ones(i.shape, dtype=numpy.byte) * 42
    ……..out[i==0] = 32
    ……..for row in out:
    …………stdout.write(row.tostring())
    …………stdout.write(’n')
    ….
    ….def start(self, x, y):
    ……..”"”x & y are now arrays of shape (nx, ny)
    ……..”"”
    ……..c = (y – 0.5) + 1.j*x
    ……..c = c.ravel()
    ……..z = numpy.zeros(c.shape, numpy.complex128)
    ……..idx = numpy.arange(z.size)
    ……..out = numpy.ones(x.shape, numpy.bool)
    ……..out_ = out.ravel()
    ……..for i in xrange(MAX_ITERATIONS):
    …………z **= 2
    …………z += c
    …………mask = (z.real + z.imag) <= BAILOUT
    …………idx, IDX = idx[mask], idx[logical_not(mask)]
    …………z = z[mask]
    …………c = c[mask]
    …………out_[IDX] = False
    ……..return out

    This now goes ~23x faster than the original CPython version (py2.6), although at the cost of less readable code. So there you have it: python is faster than java!

    (and this is why python rules for scientific and engineering type applications)

  16. Romuald
    10:59 on 30 settembre 2009

    Hello

    Here is a better perl version : http://pastebin.com/d74858d17

    Two majors changes:
    - use “my” instead of “local” (slower and doesn’t make sense in this context: it’s used to localize the value of one variable to a block, resetting is to it’s previous value once out of the block)
    - use Time::HiRes module since time() does not return microseconds

  17. Marco Mastrodonato
    14:16 on 30 settembre 2009

    @Bc
    I knew psyco JIT to optimize performance but i’ve never heard numpy before. It could be usefull for other readers, thanks.

    @Romuald
    Thank you for your improvements. I update the compare as soon as possible

  18. Michele
    17:34 on 01 ottobre 2009

    Hi bc, i tested your code but it doesn’t work.

    NameError: global name ‘MAX_ITERATIONS’ is not defined

    I added MAX_ITERATIONS = 50
    and
    NameError: global name ‘BAILOUT’ is not defined

    I added BAILOUT = 10
    and
    NameError: global name ‘logical_not’ is not defined

    Than i stopped.

    Can you send us the correct code, please?

    Bye Michele.

Lascia un commento

Ancora nessun trackback.