in reply to Re: [OT]: threading recursive subroutines.
in thread [OT]: threading recursive subroutines.
Whilst I think that your discussion of the problems is correct, I think your choice of algorithm and your code for demonstrating the problems is problematic. The latter in part because you are using Perl, which I specifically excluded; in part because you then use Coro which IMO clouds the real issues and adds more of its own.
Firstly, Fibonacci is a poor example because it is simply so easy to achieve better performance through mechanisms other than threading. As the only purpose of threading in this context is improving performance, using them for Fibonacci is never going to work effectively.
Whilst the recursive Fibonacci algorithm doesn't lend itself to tail call elimination, it is easily written iteratively for an huge performance gain. Equally, the recursive algorithm can be sped up immensely by simply memoising it.
But more importantly, the iteration/recursion can easily be done away with entirely using Binet's formula:
#! perl -slw use strict; use 5.010; use Time::HiRes qw[ time ]; use constant { GR => 1.6180339887498948482, ROOT5 => sqrt( 5 ), }; sub fibCalc { int( GR ** $_[0] / ROOT5 + 0.5 ); } sub fibIter { my $n = shift; return $n if $n < 2; my( $f1, $f2, $fib ) = ( 1, 0, 0 ); for ( 2 .. $n ) { $fib = $f1 + $f2; ( $f1, $f2 ) = ( $fib, $f1 ); } return $fib; } sub fibMem { state %cache; my $n = shift; return $n if $n < 2; return ( $cache{ $n - 2 } //= fibMem( $n - 2 ) ) + ( $cache{ $n - 1 } //= fibMem( $n - 1 ) ); } sub fibRec { my ($n) = @_; return 0 if $n == 0; return 1 if $n == 1; return fibRec($n-2) + fibRec($n-1); } printf "%2d : rec:%8d mem: %8d iter:%8d calc:%8d\n", $_, fibRec( $_ ), fibMem( $_ ), fibIter( $_ ), fibCalc( $_ ) for 1 .. 30; my $start = time; my $total = 0; $total += fibRec( $_ ) for 1 .. $ARGV[0]; printf "recursive: Got %d; took %f seconds\n", $total, time() - $star +t; $start = time; $total = 0; $total += fibMem( $_ ) for 1 .. $ARGV[0]; printf "memoized: Got %d; took %f seconds\n", $total, time() - $star +t; $start = time; $total = 0; $total += fibIter( $_ ) for 1 .. $ARGV[0]; printf "iterative: Got %d; took %f seconds\n", $total, time() - $star +t; $start = time; $total = 0; $total += fibCalc( $_ ) for 1 .. $ARGV[0]; printf "calculate: Got %d; took %f seconds\n", $total, time() - $star +t; __END__ C:\test>885839 35 1 : rec: 1 mem: 1 iter: 1 calc: 1 2 : rec: 1 mem: 1 iter: 1 calc: 1 3 : rec: 2 mem: 2 iter: 2 calc: 2 4 : rec: 3 mem: 3 iter: 3 calc: 3 5 : rec: 5 mem: 5 iter: 5 calc: 5 6 : rec: 8 mem: 8 iter: 8 calc: 8 7 : rec: 13 mem: 13 iter: 13 calc: 13 8 : rec: 21 mem: 21 iter: 21 calc: 21 9 : rec: 34 mem: 34 iter: 34 calc: 34 10 : rec: 55 mem: 55 iter: 55 calc: 55 11 : rec: 89 mem: 89 iter: 89 calc: 89 12 : rec: 144 mem: 144 iter: 144 calc: 144 13 : rec: 233 mem: 233 iter: 233 calc: 233 14 : rec: 377 mem: 377 iter: 377 calc: 377 15 : rec: 610 mem: 610 iter: 610 calc: 610 16 : rec: 987 mem: 987 iter: 987 calc: 987 17 : rec: 1597 mem: 1597 iter: 1597 calc: 1597 18 : rec: 2584 mem: 2584 iter: 2584 calc: 2584 19 : rec: 4181 mem: 4181 iter: 4181 calc: 4181 20 : rec: 6765 mem: 6765 iter: 6765 calc: 6765 21 : rec: 10946 mem: 10946 iter: 10946 calc: 10946 22 : rec: 17711 mem: 17711 iter: 17711 calc: 17711 23 : rec: 28657 mem: 28657 iter: 28657 calc: 28657 24 : rec: 46368 mem: 46368 iter: 46368 calc: 46368 25 : rec: 75025 mem: 75025 iter: 75025 calc: 75025 26 : rec: 121393 mem: 121393 iter: 121393 calc: 121393 27 : rec: 196418 mem: 196418 iter: 196418 calc: 196418 28 : rec: 317811 mem: 317811 iter: 317811 calc: 317811 29 : rec: 514229 mem: 514229 iter: 514229 calc: 514229 30 : rec: 832040 mem: 832040 iter: 832040 calc: 832040 recursive: Got 24157816; took 49.278000 seconds memoized: Got 24157816; took 0.000115 seconds iterative: Got 24157816; took 0.000416 seconds calculate: Got 24157816; took 0.000073 seconds
All of these modifications make it possible to calculate Fibonacci( 1474 ) (the limit of a doubles ability to store the result), more quickly than it is possible to spawn a thread. At least using the current implementation of iThreads. And possibly Coro also.
I realise that you chose it only as a well-known example on which to base your discussion. But if you are going to explore the threading of recursion in Perl, there are simpler ways than your implementations. This does the job:
#! perl -slw use strict; use futures; sub fibonacci { my $n = shift; return $n if $n < 2; my $r1 = futures->new( \&fib_t1, $n -2 ); my $r2 = futures->new( \&fib_t1, $n -1 ); return $r1 + $r2; } sub fib_t1 { my $n = shift; return $n if $n < 2; my $r1 = futures->new( \&fib_t2, $n -2 ); my $r2 = futures->new( \&fib_t2, $n -1 ); return $r1 + $r2; } sub fib_t2 { my $n = shift; return $n if $n < 2; return fib_t2( $n -2 ) + fib_t2( $n -1 ); } printf "$_ : %d\n", fibonacci( $_ ) for 1 .. $ARGV[ 0 ]; __END__ C:\test>885839-t 20 1 : 1 2 : 1 3 : 2 4 : 3 5 : 5 6 : 8 7 : 13 8 : 21 9 : 34 10 : 55 11 : 89 12 : 144 13 : 233 14 : 377 15 : 610 16 : 987 17 : 1597 18 : 2584 19 : 4181 20 : 6765
Where the futures module is:
package futures; use threads; sub TIESCALAR { my( $class, $thread ) = @_; return bless [ $thread ], $class; } sub STORE { die "Futures cannot be assigned to"; } sub FETCH { my $self = shift; my $r = $self->[0]->join; return $r; } sub new { my( $class, $code, @args ) = @_; my $thread = &async( $code, @args ); tie my $future, 'futures', $thread; return $future; } 1;
Using three subs serves to highlight the level dependency, but these could be merged into one sub with conditional statements provided there was some way to determine the number of existing threads.
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re^3: [OT]: threading recursive subroutines.
by Corion (Patriarch) on Feb 03, 2011 at 09:43 UTC | |
by BrowserUk (Patriarch) on Feb 03, 2011 at 10:30 UTC | |
by Corion (Patriarch) on Feb 03, 2011 at 11:43 UTC | |
|
Re^3: [OT]: threading recursive subroutines.
by ikegami (Patriarch) on Feb 03, 2011 at 08:53 UTC | |
by BrowserUk (Patriarch) on Feb 03, 2011 at 09:56 UTC | |
by BrowserUk (Patriarch) on Feb 04, 2011 at 00:07 UTC |