Clojure, Haskell & Ruby Vs Euler 25

2010-02-23 19:53:47

Haskell is a mature, statically typed, functional language which was recently compared to Ruby in an attempt to solve Euler #25. In this post I'll share the code, the benchmark and add a Clojure version for those interested.



Preface

Normally it's bad to start with the disclaimer, but for the sake of all you angry young men with ill tempers I'll say it anyway: Similar to the Clojure Vs Ruby & Scala post this is in no way an exhaustive comparison between the languages, its intended at a superficial glance at a simple challenge, namely Euler #25, all in the name of good clean fun. Haskell is by far too vast, too impressive and too powerful to be fully dealt with in a simple exercise like this, and the Ruby code is still running :)

Euler #25

Create a Fibonnaci sequence and walk through it, until you find the first number which is 1000 digits. The Fibonnacci sequence is exceedingly simple. Its seeded with 0 1 and all consecutive values n, are generated by calculating n-1 + n-2.

0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181.....


Solutions

All 3 versions are compact and powerful - I'm assuming they're idiomatic, but if I'm wrong let me know. Like I said - If you can produce a better version, please submit it instead of taking this as an invitation to a flamewar :)


Ruby

Ruby goes like this:

require 'matrix'

limit = 10**999
FIBONACCI_MATRIX = Matrix[[1,1],[1,0]]
def fibonacci(n)
  (FIBONACCI_MATRIX**(n-1)) [0,0]
end
i = 1
i+=1 while fibonacci(i) < limit
puts i

The real trick here is building the Fibonnacci seq (fib-seq), which is done by applying the Matrix form, which is a clever way to leverage some differential equations in order to produce the sequence. According to the author this runs faster than a regular memoized variant.


Haskell

Haskell code looks very different from both Ruby and Clojure, but for a simple task like this is reads almost like plain English:

limit = 10^999
fibonacci_numbers = 0:1:(zipWith (+)
                        fibonacci_numbers (tail fibonacci_numbers))

index = length w where w = takeWhile (< limit) fibonacci_numbers

main = do
  putStrLn(show(index))

First the limit is defined as the first number having 1000 digits, and then a sequence seeded with 0 and 1. When you have a sequence which requires a constant look-behind of n-values, it can be represented as a recursive set of vectors. I call that a rule of thumb, Christophe says its a theorem. One thing which is really great about the Haskell code, is the automatic currying which enables the author to do the elegant w = takeWhile (< limit). Although this example is not flaunting it, Haskell is very different from Clojure in that its statically typed and there's been many discussions back and forth on which is to prefer, dynamic or static typing. The truth is, no one answer fits all but there's a simple test:


Static Typing


If the arrow and the explanation actually helped you, then static typing is for you.


Clojure

To produce the fib-seq, we can mimic Haskell almost point for point, but where Haskell has direct support for sequences in the syntax, I need to call a few functions:

(def fib-seq (lazy-cat [0 1] (map + fib-seq (rest fib-seq))))

(let [limit (.pow (BigInteger/TEN) 999)] 
           (count (take-while #(< % limit) fib-seq)))

The noticeable exterior differences is the call to lazy-cat and the anonymous function, which all readers of this blog should be able to read without any explanation. Under the hood, Haskell is fully lazy where Clojure is now Chunky-lazy. Chunked-seqs mean that I work with the sequences as if they were fully lazy, but under the hood Clojure is realizing the sequence, one chunk at a time. If I take 50 values, 82 may in fact be calculated - this is for improved performance without wrecking the use of infinite sequences. According to one of the authors of this book (which looks extremely promising btw, so go look), Rich Hickey has provided an example which eliminates the need for chunked seqs: here. That is idiomatic Clojure running faster than our old seq implementation written primarily in Java.


Results

So the usual indicators for expressiveness and performance are LOC and runtime speed, so here we go:

Language LOC Runtime
Clojure 3 0.021s
Haskell 6 0.007s
Ruby 9 7.1s


Last time I benchmarked Ruby it also came in... a little late, and some commentators suggested trying out other Ruby compilers - The truth is however, that small benchmarks like this really show very little and if you are working in a performance critical zone, you'll want to test something which mimics the actual project and the actual environment. Secondarily, neither Haskell nor Clojure are optimized for performance which accounts for some of the extra Ruby code.

The lesson we can take away, is that Haskell and Clojure are blazing, and Haskell is blazing to the point where I'm not finding words good enough to describe it.

Dhananjay Nene
2010-02-23 22:08:24
This code in python clocks in at 3.5 milliseconds on my notebook (which is not particularly powerful)

def fib():
    x = 1
    y = 1
    while True :
        x,y = y,x+y
        yield x

limit = 10 ** 999
for cycle, num in enumerate(fib()) :
    if num &gt; limit :
        print cycle
        break
Lau
2010-02-23 22:11:33
Dhananjay, thanks for contributing - It runs at just under 0.9s here.
Dhananjay Nene
2010-02-23 22:17:46
Lau,

Here's the actual code with all the benchmarking hooks - its still 3.5 milliseconds per iteration

import time

def fib():
    """ Fibonacci series generator """
    x = 1
    y = 1
    while True :
        x,y = y,x+y
        yield x

cycle = 0
iterations = 10000
start = time.time()
limit = 10 ** 999
for iter in xrange(iterations) :
    for cycle, num in enumerate(fib()) :
        if num &gt; limit :
            break
end = time.time()
print cycle
print "%s milliseconds" % str((end - start) * 1000 / iterations)
num = 0
Sean Devlin
2010-02-23 22:34:34
Cool post :)

Silly question - Should the limit be 10^1000?  I think you code finds the first 999 digit number.

Also, I was wondering why you compute the Clojure limit each time (I think!).  I'd use a let like this:

(let [limit (.pow (BigInteger/TEN) 1000)]
  (count (take-while #(&lt; % limit) fib-seq)))

Do type hints help performance as well?
Joel Mueller
2010-02-23 22:45:47
This single line of F# code takes 0.013 seconds to arrive at the answer on my machine. Although in practice I would normally break it up into 4 lines.

let euler25 = Seq.unfold (fun (n0, n1) -&gt; Some(n0, (n1, n0 + n1))) (0I,1I) |&gt; Seq.takeWhile ((&gt;) (pown 10I 999)) |&gt; Seq.length;;
Lau
2010-02-23 22:48:08
Hi Sean,

Thanks for stopping by. The limit is 10^999 because thats what the others use, and:

user&gt; (count (str (.pow (BigInteger/TEN) 999)))
1000

I dont think that type hints will do us any good, but you're welcome to prove me wrong. I was expected HotSpot to pick up the repeated generation of the same number, but wrapping it in a let does improve performance so much, that I've updated the post and added another line of code :) Thanks for pointing it out.
Dhananjay Nene
2010-02-23 22:55:14
Here's a 2 line python code clocking approx 3 miliseconds for a single iteration even when run only once ie. no batched iterations (on rare occasion extending to 5 milliseconds)

x, y = 1,1
while x &lt; 10 ** 999 : x,y = y,x+y

If you want to measure the cycle count too, the code is

x,y,c = 1,1,1
while x &lt; 10 ** 999 : x,y,c = y,x+y,c+1

I would however suggest that you do run each program in a large batch of iterations (say 10000 for this example). That would allow the JRE to identify the hotspot and allow it to JIT the necessary functions to be able to get a much better time for clojure.
Lau
2010-02-23 22:56:51
Dhananjay, much nicer. Comes in at 0.034s, about 1/3 slower than the updated Clojure (see the post).
Sean Devlin
2010-02-23 23:01:48
Oh, right.  10^6 has 7 digits.  My mistake.  Also,  I didn't notice any difference w/ type hinting.
Sean Devlin
2010-02-23 23:04:57
Oh, and a version using partial is coming in about 50% slower than #(%).

(let [limit (.pow (BigInteger/TEN) 999)]
           (count (take-while (partial &gt;= limit) fib-seq))))
Lau
2010-02-23 23:05:58
Wow, thats really sad :)
Dhananjay Nene
2010-02-23 23:24:06
Lau,

Can you describe your benchmarking methodology. Three specific questions :

a. Do your timings include program startup and shutdown times ?
b. How many times do you iterate over the desired code segment within a program ?
c. What do you use to measure time - is it the command line "time" for unix or is it an OS system call to then compute the differences ?

Dhananjay
Don Stewart
2010-02-24 00:13:11
Your Haskell would be better written as:

limit = 10^999

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
 
main = print .  length . takeWhile (&lt; limit) $ fibs

-- compile with ghc -O2 --make

And please make sure you list which versions of the tools, what compile flags you use, and on which architecture.
Joe Zack
2010-02-24 00:37:28
I don't know if it's any more idiomatic, but I was able to get much faster results by brute forcing in ruby. 

7 LOC, and about .024 ms on my computer

max  = 10 ** 999  
a, b, i = 1, 1, 2  
  
while b &lt; max  
  i += 1  
  a, b = b, b + a  
end  

puts i

http://joezack.com/index.php/2009/05/09/project-euler-problem-25-in-ruby/
Arthur Ulfeldt
2010-02-24 00:43:45
generating a one-off sequence will save a lot of garbage collection cycles if you are not going to search it often:

(defn get-fib-seq []
    (lazy-cat [0 1] (map + fib-seq (rest fib-seq))))

(let [limit (.pow (BigInteger/TEN) 999)]
     (count (take-while #(&lt; % limit) (get-fib-seq))))
Aaron
2010-02-24 00:50:56
Total shenanigans on the Ruby code;

just by eyeball the following is substantially faster on my machine, although somewhat less idiomatic for Ruby.  I will admit, I did not actually look at timers - it is possible the original implementation lost a lot of time just including matrix.

limit = 10**999

def fib2(a,b)
	return b,b+a
end

a,b = fib2(0,1)
while b &lt; limit
	a,b = fib2(a,b)
end
puts b
Brian Takita
2010-02-24 00:52:20
The Ruby solution in the blog post is terrible. You should use the "brute force" approach posted by Joe Zack. Much simpler.
Aaron
2010-02-24 01:11:47
Ok, I got interested enough to throw ruby-prof at it.  The Matrix-based Ruby algorithm above is being misused.  The matrix expression allows for calculation of the Nth value of the Fibonacci sequence, but is being called N times on the way up to the target value, giving classic O(N^2) performance.

To give that a fare shake compared to the other implementations it should be called once with the (magic) value of 4782, as that is the Fibonacci number you are looking for.

The "magic value" version of the Ruby Matrix Fibonacci clocks in at 0.01 sec via ruby-prof on my machine.

If including the magic value is cheating, the simple sequential implementation I posted above still only clocks in at 0.047s on my machine via ruby-prof.  Still pokey compared to Haskell et,al perhaps, but not the eyebrow-raising 3-orders-of-magnitude pokey reported above.
Kamatsu
2010-02-24 01:44:53
Your LOC measure for haskell is a bit misleading. Here it is similar to the size of clojure:

fib_numbers = 0:1:(zipWith (+) fib_numbers (tail fib_numbers))
main = print $ length $ let limit = 10^999 
                        in takeWhile (&lt; limit) fibonacci_numbers
laulau
2010-02-24 01:48:07
The Haskell version don't seem idiomatic. Try this:

main = putStrLn . show . length $ takeWhile (&lt; l) fibs
  where l = 10 ^ 999
         fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
domor
2010-02-24 02:17:04
To add to Don's haskell version, here is one that uses Criterion for benchmarking, as shown in his own blog post ( http://donsbot.wordpress.com/2010/02/23/modern-benchmarking-in-haskell/ ). Returns 145 μs per call on my Core 2 duo laptop. Do "cabal install criterion" if needed, then compile with "ghc --make -Odph fib.hs" and run.

import Criterion.Main

limit = 10^999 :: Integer
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
fibOver n = length . takeWhile (&lt;n) $ fibs

main = defaultMain [ bench &quot;Fibs until 10^999&quot; $ nf fibOver limit ] 
Hugo Vincent
2010-02-24 02:39:52
I don't understand how you counted LOC... the Haskell and Ruby examples include the putStrLn/puts but the Clojure example doesn't. And the Clojure example collapses the line limit = 10^999 inline. So to fairly compare the Haskell and Clojure examples, you get 3 LOC for both.
flag
2010-02-24 02:58:50
the ruby version is bad, better is

t=Time.now
a=0
b=1
limit=10**999
i=1
i+=1 while limit&gt;a=b+b=a
p i
p Time.now-t

produces:
4782
0.00861
Don Stewart
2010-02-24 03:37:04
And, finally, a nice paper from 20 years ago on why you should never, ever ever use fibonacci functions as a "benchmark". Let alone a comparative language benchmark... !

http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.53.4124
Mark Reid
2010-02-24 03:38:56
I realise this is "cheating" a bit but there is even a faster way to compute the n such that F(n) has at least 1,000 digits.

There is a closed form expression for the nth Fibonnaci number in terms of φ=(1+√5)/2 which is F(n) = ⎣φ^n / √5 + 0.5⎦ where ⎣x⎦is the "floor of x" - the largest integer smaller than x.

All we do now is solve F(n) = ⎣φ^n / √5 + 0.5⎦&gt;10^999 which holds if φ^n / √5 &gt; 10^999 so that n &gt; (ln(√5)+999 ln(10))/ln(φ) = 4781.859 and so n must be 4782.

If you want clojure code for computing the first index n such that F(n) has k digits its:

(def sqrt5 (Math/sqrt 5))
(def phi (/ (+ 1 sqrt5) 2))
(defn ln [x] (Math/log x))
(defn fib-index [digits]
   (Math/ceil 
      (/ (+ (ln sqrt5) 
            (* (dec digits) (ln 10)))
         (ln phi))))

I haven't timed it but I suspect it may be faster than the other versions above, especially if you want the index of the first Fibonnaci number with a million or a billion digits.

:)
Dorian
2010-02-24 04:14:53
Here's a one line Ruby based on Dhananjay's Python in #7

x, y, z = 1, 1, 10 ** 999; x, y = y, x + y while x &lt; z

timed with

start = Time.now
x, y, z = 1, 1, 10 ** 999; x, y = y, x + y while x &lt; z
stop = Time.now
puts &quot;#{stop - start}&quot;

On my machine:

0.004s to 0.005s

Printing the cycle count adds about one millisecond:

w, x, y, z = 1, 1, 1, 10 ** 999; w, x, y = w + 1, y, x + y while x &lt; z; puts w

And when running with `time`

$ time ruby euler25.rb
4782

real	0m0.021s
user	0m0.012s
sys	0m0.007s

For the haskell, taken directly from your example, compiled and run with time 

$ time ./euler25
4782

real	0m0.009s
user	0m0.003s
sys	0m0.004s

Also for comparison

$ time runghc euler25.hs 
4782

real	0m0.204s
user	0m0.150s
sys	0m0.043s

These are obviously extremely casual benchmarks and, as Don notes, this is not a reasonable way to compare languages, but it&#039;s clear that your number for Ruby is more than a couple orders of magnitide off. And while I haven&#039;t used clojure, the Ruby and Haskell number suggest it&#039;s possible that Ruby is comparable to or even faster than it at this.
Rahul G
2010-02-24 05:15:17
Scala version (Translation of Joe Zack's Ruby code) :
http://ideone.com/Tt4Yw0Ys

Time : 0.011 secs
klodolph
2010-02-24 08:01:53
That's a pretty verbose Haskell solution.  It's kind of strange to put "10^999" and "do" on their own lines.  Also, "putStrLn . show" is just a long way of saying "print", and the "do" is unnecessary.

fibs = 0 : 1 : zipWith (+) fibs (tail fibs) :: [Integer]
main = print $ length $ takeWhile (&lt; 10^999) fibs

Of course, this isn&#039;t really a good way to compare performance.  Most of the performance differences are going to come from start up times with samples this small, especially since Ruby and Clojure have much heavier runtimes than Haskell.  You&#039;ll be spending most of your time in the bigint library, which probably isn&#039;t even written in the language.  GHC uses GMP, which is written in C and assembler.  It would be fun, though, to crank up the limit.

Incidentally, the index of the first fibonacci number with 10^(10^1000) digits is:

47849719667816659713581897523767369103156029829682320941041882785231598969359813907939008873229041827888848853486754792555026732216411757899251191789588613959426661731474396755757693080392981559045529812420404638172031086668518709036485138118794903322420549152338402651558857966786690562252918311039837716266174837546462385507432661421687267867873969601145517420378460729954028811250104615301497283903396121915993502128184741532384412788320478931279457506480801636710528553755350502913429822962882622631516947328398151316642774090223852684893507696669135966544372468052153474172693435960469463162190203829854221940775559122828094891978932902236807701844525775774951068481444743707927468729967181250287889414617849780330643160299323907393716188922393236696374692256188926255818672435587427975326072586233935615753450785298211257838792788620226051900372479847977016721793305820403329620695526061541002728544841347932722224415620366435944427711964575216360229550104587435249638108239912432570285901099690

real	0m0.059s
user	0m0.022s
sys	0m0.010s

Yes, the value is exact.
Adityo
2010-02-24 08:29:27
Hey Lau,

According to the euler problem, the 12th Fib number is 144, but in your infinite sequence the 12th Fib number is 89, 

Here's my version

(def fibs (map second (iterate (fn [[a b]] [b (+ a b)]) [0 1])))

(let [limit (.pow (BigInteger/TEN) 999)]
              (count (take-while #(&lt; % limit) fibs)))

running it a few times, i managed 

(time (let [limit (.pow (BigInteger/TEN) 999)]
              (count (take-while #(&lt; % limit) fibs))))
&quot;Elapsed time: 3.839175 msecs&quot;

--regards
Adie
Duc
2010-02-24 09:07:40
Not as short as the clojure from the article, but easier to understand (maybe):


  user&gt; (time (let [limit (.pow (BigInteger/TEN) 999)]
                 (loop [a 0 b 1 i 1]
                    (if (&lt; b limit)
                        (recur b (+ a b) (inc i))
                        i))))
  &quot;Elapsed time: 3.554351 msecs&quot;
  4782
Tommi
2010-02-24 11:17:39
Duc already said it, but IMHO tail recursion is the best way to do it.

PLT Scheme, 3 milliseconds:

(define num (expt 10 1000))

(define (f n)
  (if (&gt;= (cdr n) num)
      (cdr n)
      (f (cons (cdr n) (+ (cdr n) (car n))))))

(define (aaa) (f '(0 . 1)))
jazen
2010-02-24 11:57:00
I don't know exactly what Haskell is doing, but i guess it's a safe bet it does some optimizations/caching when using zipWith/tail.

Method calls are expensive in any language and there are an awful lot of them when using naive recursion without caching/memoization.

Here's a simple Ruby version w/ memoization, which clocks in at ~0.03s: http://gist.github.com/313326
Laurent Petit
2010-02-24 14:37:43
Hi,

Talking about idiomatic Clojure, I guess your example is a bit misleading since it gives the sequence a global name : the head of the sequence will be kept forever ...

Instead of 
(def fib-seq (lazy-cat [0 1] (map + fib-seq (rest fib-seq))))

(let [limit (.pow (BigInteger/TEN) 999)]
           (count (take-while #(&lt; % limit) fib-seq)))

I would suggest having a function create the fib-seq:
(use &#039;clojure.contrib.seq)
(defn fib-seq [ ] (rec-cat fib [0 1] (map + fib (rest fib))))

(let [limit (.pow (BigInteger/TEN) 999)]
           (count (take-while #(&lt; % limit) (fib-seq))))

Not sure if it helps clojure go up in the benchmark, though ;-)

My 0.02€,

-- 
Laurent
Timothy Allen
2011-02-08 05:34:28
A more concise Haskell version

fibs = 0 : scanl (+) 1 fibs
main = putStrLn $ show $ length $ takeWhile (< 10^999) fibs

2 Lines
20 Words
87 Characters