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

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!
English
Italiano
informatimago
24 August 2009 at 13:27
Oops, Please, add this line at the beginning of the bench1.lisp:
(declaim (optimize (speed 3) (space 2) (debug 0) (safety 0)))
informatimago
24 August 2009 at 13:28
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.
Marco Mastrodonato
24 August 2009 at 15:16
Unfortunately, your comment has been catch by antispam, i will add lisp asap and thanks for your work
Piyush
25 August 2009 at 19:55
Did you run jruby with –server command line switch ?
lrz
26 August 2009 at 01:07
Too bad you’re testing this on Windows, because MacRuby gives 0.345s here (vs 1.930s for 1.9.2dev)
Dino Viehland
26 August 2009 at 04:49
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).
Marco Mastrodonato
26 August 2009 at 22:20
@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
Isaac Gouy
27 August 2009 at 03:14
> 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
truncatei
27 August 2009 at 11:55
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);
Marco Mastrodonato
27 August 2009 at 14:15
@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!
Isaac Gouy
27 August 2009 at 17:37
> 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.
Marco Mastrodonato
27 August 2009 at 22:00
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.
Marco Mastrodonato
27 August 2009 at 22:48
@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.
bc
20 September 2009 at 10:23
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.
bc
20 September 2009 at 14:31
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)
Romuald
30 September 2009 at 10:59
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
Marco Mastrodonato
30 September 2009 at 14:16
@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
Michele
1 October 2009 at 17:34
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.