in reply to fibonacci numbers using subroutine?

Ok, I'll bite... The fibonacci sequence is often used in CS classes as "the poster child against recursion" because the recursive algorithm is just so horrific in performance.

Below I have coded both a non-recursive and a recursive algorithm in pretty much "run of the mill" implementations. They both produce the same results although the recursive algorithm is ridiculously slow.

Sorry I didn't crank up the iterations far enough to get a meaningful result for the non-iterative version because if I did that, I'd have to go take a long nap waiting for the recursive version to finish! However, the benchmark below shows just how silly the comparison between the two is. "Un-comment" the print statements and crank down the iterations to verify output is the same.

#!/usr/bin/perl -w use strict; use Benchmark; timethese (10, { Normal=> q{ foreach (20..25) { #print "number $_ : fibonacci ",fibonacci($_),"\n"; my $f = fibonacci($_); } }, Slow => q{ foreach (20..25) { #print "number $_ : fibonacci ",fibonacci_slow($_),"\n"; my $f = fibonacci_slow($_); } }, } ); =prints: Benchmark: timing 10 iterations of Normal, Slow... Normal: 0 wallclock secs ( 0.00 usr + 0.00 sys = 0.00 CPU) (warning: too few iterations for a reliable count) Slow: 7 wallclock secs ( 6.24 usr + 0.00 sys = 6.24 CPU) @ 1 +.60/s (n=10) =cut sub fibonacci { my $number = shift; my $curnum = 1; my $prevnum = 1; my $sum; if ($number ==1 || $number ==2){ return 1;} $number -= 2; while ($number--) { $sum = $curnum + $prevnum; $prevnum = $curnum; $curnum = $sum; } return $sum; } sub fibonacci_slow { my $number = shift; if ($number ==1 || $number ==2){ return 1;} return fibonacci_slow($number-1) + fibonacci_slow($number-2); }

Replies are listed 'Best First'.
Re^2: fibonacci numbers using subroutine?
by BrowserUk (Patriarch) on Aug 20, 2010 at 10:31 UTC

    A simply memoised recursive fib will outstrip most iterative methods:

    sub fib_memo { state $memo = { 1 => 1, 2 => 1 }; return $memo->{ $_[0] } //= fib_memo( $_[0] - 1 ) + fib_memo( $_[0 +] - 2 ); }
      Interesting. I ran same benchmark as I'd run previously. Of course I cranked up the number of iterations and took the slow recursive version out of the mix otherwise I'd have time to go have a vacation in Mexico or something!

      For run #1, as more of an apples to apples deal, I flushed the cache after each subroutine call. The memoized version wound up about 1/2 the speed (1495/3478) of the iterative version which is a pretty impressive result considering how incredibly slow the non-memoized version is!

      Then for run #2, I commented out the line with the "flush" and let fib_memo do its best. Not surprisingly the iterative version stayed the same while the memoized version sped up (4130/3516).

      My curiosity is satisfied, but code is below if anybody wants to fiddle with it.

      #!/usr/bin/perl -w use strict; use Benchmark; use Memoize qw(flush_cache memoize); use feature "state"; memoize ('fib_memo'); timethese (10000, { Normal=> q{ foreach (20..25) { #print "number $_ : fibonacci ",fibonacci($_),"\n"; my $f = fibonacci($_); } }, # Slow => # q{ # foreach (20..25) # { # #print "number $_ : fibonacci ",fibonacci_slow($_),"\n"; # my $f = fibonacci_slow($_); # } # }, Memo => q{ foreach (20..25) { #print "number $_ : fibonacci ",fib_memo($_),"\n"; my $f = fib_memo($_); flush_cache('fib_memo'); } }, } ); =prints Benchmark: timing 10000 iterations of Memo, Normal... Memo: 7 wallclock secs ( 6.69 usr + 0.00 sys = 6.69 CPU) @ 14 +95.44/s (n=10000) Normal: 3 wallclock secs ( 2.88 usr + 0.00 sys = 2.88 CPU) @ 34 +78.26/s (n=10000) Just for fun...without flush_cache('fib_memo'); Benchmark: timing 10000 iterations of Memo, Normal... Memo: 2 wallclock secs ( 2.42 usr + 0.00 sys = 2.42 CPU) @ 41 +30.52/s (n=10000) Normal: 3 wallclock secs ( 2.84 usr + 0.00 sys = 2.84 CPU) @ 35 +16.17/s (n=10000) =cut sub fibonacci { my $number = shift; my $curnum = 1; my $prevnum = 1; my $sum; if ($number ==1 || $number ==2){ return 1;} $number -= 2; while ($number--) { $sum = $curnum + $prevnum; $prevnum = $curnum; $curnum = $sum; } return $sum; } sub fibonacci_slow { my $number = shift; if ($number ==1 || $number ==2){ return 1;} return fibonacci_slow($number-1) + fibonacci_slow($number-2); } sub fib_memo { state $memo = { 1 => 1, 2 => 1 }; return $memo->{ $_[0] } //= fib_memo( $_[0] - 1 ) + fib_memo( $_[0 +] - 2 ); }

        You don't need to Memoize fib_memo(), the memoisation is already built in! That's what the state variable %memo is for.

        Once you remove that, the real performace shows up (This is for (1..100)):

        c:\test>junk21 Benchmark: timing 10000 iterations of Memo, Normal... Memo: 1 wallclock secs ( 0.64 usr + 0.00 sys = 0.64 CPU) @ 15 +649.45/s (n=10000) Normal: 15 wallclock secs (14.98 usr + 0.01 sys = 14.99 CPU) @ 66 +7.07/s (n=10000)

        The difference is all the clearer with my preferred cmpthese() (why do you use timethese()?):

        c:\test>junk21 Rate Normal Memo Normal 669/s -- -96% Memo 15573/s 2229% --
Re^2: fibonacci numbers using subroutine?
by Anonymous Monk on Aug 20, 2010 at 05:32 UTC
    Memoize can help on the speed front :)
      I agree. Memoize can help a lot. Here's a little script that I use to get the 1000th, 2000th, 3000th number etc.
      #!/usr/bin/perl use strict; use warnings; use Math::Big qw/fibonacci/; use Memoize; memoize('fib', INSTALL => 'fastfib'); my $n = '1000'; print fib(), "\n"; sub fib { foreach ($n) { my @fibonacci = fibonacci($n); print "$fibonacci[$n-1]", "\n"; } }
        I thought this code looked a bit odd, so I took the trouble to install Math::Big and play with it.

        1. I think you have an "off by one" problem. $fibonacci[$n-1] does not appear to be correct.
        2. The need for the fib subroutine is unclear to me.
        3. This fibonacci subroutine in the Math::Big library appears to be so fast, that the need for Memoize is also unclear to me...this thing barfs out fibonacci(1000) with incredible speed!

        Below is my testing code:

        #!/usr/bin/perl use strict; use warnings; use Math::Big qw/fibonacci/; my $n=6; print scalar(fibonacci($n)), "\n"; print "".fibonacci($n), "\n"; #another way to force scalar context my @fibs = fibonacci($n); print "@fibs\n"; print $fibs[-1],"\n"; #NOT $n-1, [$n] same as [-1] here. foreach (1000,2000,3000) { #print "".fibonacci($_), "\n\n"; #prints HUGE numbers not shown below } __END__ prints: .....testing the fibonacci of 6, which is indeed 8.... 8 8 0 1 1 2 3 5 8 8
        Anyway, I figure that we are getting pretty "far into the outfield" on this one. Yes, there is a library function for generating a fibonacci number. Yes, it is very fast. Yes, it can calculate these numbers to an incredible number of digits. No, it does not use a recursive algorithm. The library function likes to start the sequence at 0 instead of 1, but that is allowed within the definition of the sequence. Of course it is completely possible that I've misunderstood something - wouldn't be the first time...let me know.