Re: Bouncing on trampolines (so cool...)

2013-05-16 Thread Alexander Burger
Hi all,

I didn't bother with trampolines so far, but add some comments to

On Thu, May 16, 2013 at 12:00:20AM +0800, Samuel Dennis Borlongan wrote:
 ...
 http://stackoverflow.com/questions/12186672/how-can-i-prevent-my-ackerman-function-from-overflowing-the-stack
 ...

The bad thing with overflowing your stack is ... well, overflowing your
stack.

But you may as well overflow your heap, so nothing is gained.

As it is recommended for PicoLisp anyway to set 'ulimit -s unlimited',
the stack will use as much memory as is physically available, like the
heap.

The recursive version is usually faster than the one using an explicit
stack, but uses perhaps a little bit more memory because each recursion
level takes several stack lokations.


Let's take the standard Ackermann function

   (de ack (M N)
  (cond
 ((=0 M) (inc N))
 ((=0 N) (ack (dec M) 1))
 (T (ack (dec M) (ack M (dec N ) )

Eliminating tail-recursion gives

   (de ack2 (M N)
  (loop
 (T (=0 M) (inc N))
 (NIL (=0 N) (ack (dec M) (ack M (dec N
 (dec 'M)
 (one N) ) )

Both functions show roughly the same speed,

   : (bench (ack 4 1))
   249.465 sec
   - 65533

   : (bench (ack2 4 1))
   252.487 sec
   - 65533

though the less-recursive function is slightly slower (as expected).


If we go - as usually recommended - for the iterative version with an
explict stack,

   (de ack3 (M N)
  (for (Stack (cons M)  Stack)
 (if (=0 (setq M (pop 'Stack)))
(inc 'N)
(push 'Stack (dec M))
(if (=0 N)
   (one N)
   (push 'Stack M)
   (dec 'N) ) ) ) )

things get significantly slower:

   : (bench (ack3 4 1))
   312.028 sec
   - 65533


The fastest is - as expected and described - the cheating
version:

   (de ack4 (M N)
  (for (Stack (cons M)  Stack)
 (cond
((=0 (setq M (pop 'Stack))) (inc 'N))
((= 1 M) (inc 'N 2))
((= 2 M) (setq N (+ 3 (* 2 N
(T
   (push 'Stack (dec M))
   (if (=0 N)
  (one N)
  (push 'Stack M)
  (dec 'N) ) ) ) ) )

With that we get

   : (bench (ack4 4 1))
   0.000 sec
   - 65533

   : (bench (length (ack4 4 2)))
   0.981 sec
   - 19729


 (de ack/t (M N R S)
   (default R '())
   (if ( (length R) 0)
 (unless S
   (setq M (pop 'R) ) )
 (push 'R M) )
   (cond ((=0 (length R) ) (done N) )
 ((= M 0) (continue ack/t M (+ N 1) R) )
 ((= M 1) (continue ack/t M (+ N 2) R) )
 ((= M 2) (continue ack/t M (+ (* N 2) 3) R) )
 ((= N 0) (continue ack/t (- M 1) 1 R T) )
 (T
   (push 'R (- M 1) )
   (continue ack/t M (- N 1) R T) ) ) )
 
 Honestly, I hate my Ackermann implementation, not because its performance
 is bad ( (trampoline ack/t 3 8000) takes 1.970 sec and 10.287 sec in 32-bit
 and ersatz, respectively)

Are you sure? Here I get

   : (bench (length (ack4 3 8000)))
   0.018 sec
   - 2410

and even on my humble Netbook with a slow Atom CPU (pil32):

   : (bench (length (ack4 3 8000)))
   0.213 sec
   - 2410

♪♫ Alex


Side note (though this doesn't really matter for the speed): It is never
wise to use 'length' to detect short lists. Better replace

   (if (=0 (length R)) ..)with  (if R ..)

or

   (if (= (length R) 3) ..)  with  (if (cddr R) ..)

to avoid possibly counting along long lists.
-- 
UNSUBSCRIBE: mailto:picolisp@software-lab.de?subject=Unsubscribe


Re: Bouncing on trampolines (so cool...)

2013-05-16 Thread Alexander Burger
On Thu, May 16, 2013 at 01:33:54PM +0200, Alexander Burger wrote:
: (bench (ack 4 1))
249.465 sec
- 65533
 
: (bench (ack2 4 1))
252.487 sec
- 65533
 
 though the less-recursive function is slightly slower (as expected).

Oops, forget that! Not as expected ... the opposite would be expected.
But the values are very close anyway.

♪♫ Alex
-- 
UNSUBSCRIBE: mailto:picolisp@software-lab.de?subject=Unsubscribe


Re: Bouncing on trampolines (so cool...)

2013-05-16 Thread Samuel Dennis Borlongan
Re: side-note

(bench (length (ack4 3 8000))) (32-bit Cygwin)
0.070 sec

(bench (length (trampoline ack/t 3 8000))) (32-bit Cygwin)
2.010 sec

(bench (length (trampoline ack/t 3 8000))) (64-bit Ersatz on Java 8, before
=0 and length invocation removal)
9.625 sec

(bench (length (trampoline ack/t 3 8000))) (64-bit Ersatz on Java 8, after
=0 invocation removal)
4.981 sec

(bench (length (trampoline ack/t 3 8000))) (64-bit Ersatz on Java 8, after
=0 and length invocation removal)
0.123 sec

I therefore conclude that ack/t was programmed badly. My fault.
Also, efficient programming in Ersatz apparently _relies_ on things that
might speed up a little.

Trampolines are still fun, though.

New version:

(de ack/t (M N R S)
  (default R '())
  (if R
(unless S
  (setq M (pop 'R) ) )
(push 'R M) )
  (cond ((not R) (done N) )
((= M 0) (continue ack/t M (+ N 1) R) )
((= M 1) (continue ack/t M (+ N 2) R) )
((= M 2) (continue ack/t M (+ (* N 2) 3) R) )
((= N 0) (continue ack/t (- M 1) 1 R T) )
(T
  (push 'R (- M 1) )
  (continue ack/t M (- N 1) R T) ) ) )

Thanks for the comments!

P.S.
For the sake of context and gratuitous post-scripts, most of my work is
done using Ersatz PicoLisp with LispIDE / Notepad++ on Windows workstations
that run pre-release Java 8 and 108-tab Firefox.

Again, thanks!



Samuel Dennis R. Borlongan


On Thu, May 16, 2013 at 7:33 PM, Alexander Burger a...@software-lab.dewrote:

 Hi all,

 I didn't bother with trampolines so far, but add some comments to

 On Thu, May 16, 2013 at 12:00:20AM +0800, Samuel Dennis Borlongan wrote:
  ...
 
 http://stackoverflow.com/questions/12186672/how-can-i-prevent-my-ackerman-function-from-overflowing-the-stack
  ...

 The bad thing with overflowing your stack is ... well, overflowing your
 stack.

 But you may as well overflow your heap, so nothing is gained.

 As it is recommended for PicoLisp anyway to set 'ulimit -s unlimited',
 the stack will use as much memory as is physically available, like the
 heap.

 The recursive version is usually faster than the one using an explicit
 stack, but uses perhaps a little bit more memory because each recursion
 level takes several stack lokations.


 Let's take the standard Ackermann function

(de ack (M N)
   (cond
  ((=0 M) (inc N))
  ((=0 N) (ack (dec M) 1))
  (T (ack (dec M) (ack M (dec N ) )

 Eliminating tail-recursion gives

(de ack2 (M N)
   (loop
  (T (=0 M) (inc N))
  (NIL (=0 N) (ack (dec M) (ack M (dec N
  (dec 'M)
  (one N) ) )

 Both functions show roughly the same speed,

: (bench (ack 4 1))
249.465 sec
- 65533

: (bench (ack2 4 1))
252.487 sec
- 65533

 though the less-recursive function is slightly slower (as expected).


 If we go - as usually recommended - for the iterative version with an
 explict stack,

(de ack3 (M N)
   (for (Stack (cons M)  Stack)
  (if (=0 (setq M (pop 'Stack)))
 (inc 'N)
 (push 'Stack (dec M))
 (if (=0 N)
(one N)
(push 'Stack M)
(dec 'N) ) ) ) )

 things get significantly slower:

: (bench (ack3 4 1))
312.028 sec
- 65533


 The fastest is - as expected and described - the cheating
 version:

(de ack4 (M N)
   (for (Stack (cons M)  Stack)
  (cond
 ((=0 (setq M (pop 'Stack))) (inc 'N))
 ((= 1 M) (inc 'N 2))
 ((= 2 M) (setq N (+ 3 (* 2 N
 (T
(push 'Stack (dec M))
(if (=0 N)
   (one N)
   (push 'Stack M)
   (dec 'N) ) ) ) ) )

 With that we get

: (bench (ack4 4 1))
0.000 sec
- 65533

: (bench (length (ack4 4 2)))
0.981 sec
- 19729


  (de ack/t (M N R S)
(default R '())
(if ( (length R) 0)
  (unless S
(setq M (pop 'R) ) )
  (push 'R M) )
(cond ((=0 (length R) ) (done N) )
  ((= M 0) (continue ack/t M (+ N 1) R) )
  ((= M 1) (continue ack/t M (+ N 2) R) )
  ((= M 2) (continue ack/t M (+ (* N 2) 3) R) )
  ((= N 0) (continue ack/t (- M 1) 1 R T) )
  (T
(push 'R (- M 1) )
(continue ack/t M (- N 1) R T) ) ) )
 
  Honestly, I hate my Ackermann implementation, not because its performance
  is bad ( (trampoline ack/t 3 8000) takes 1.970 sec and 10.287 sec in
 32-bit
  and ersatz, respectively)

 Are you sure? Here I get

: (bench (length (ack4 3 8000)))
0.018 sec
- 2410

 and even on my humble Netbook with a slow Atom CPU (pil32):

: (bench (length (ack4 3 8000)))
0.213 sec
- 2410

 ♪♫ Alex


 Side note (though this doesn't really matter for the speed): It is never
 wise to use 'length' to detect short lists. Better replace

(if (=0 (length R)) ..)with  (if R ..)

 or

(if (= (length R) 3) ..)  with  (if (cddr R) 

Re: Bouncing on trampolines (so cool...)

2013-05-16 Thread Alexander Burger
Hi Samuel,

 (bench (length (trampoline ack/t 3 8000))) (64-bit Ersatz on Java 8, before
 =0 and length invocation removal)
 9.625 sec
 
 (bench (length (trampoline ack/t 3 8000))) (64-bit Ersatz on Java 8, after
 =0 invocation removal)
 4.981 sec
 
 (bench (length (trampoline ack/t 3 8000))) (64-bit Ersatz on Java 8, after
 =0 and length invocation removal)
 0.123 sec

Thanks!

Interesting, I wouldn't have expected the effect be so big. The stacked
list in 'R' must be very long then.

♪♫ Alex
-- 
UNSUBSCRIBE: mailto:picolisp@software-lab.de?subject=Unsubscribe


Re: Bouncing on trampolines (so cool...)

2013-05-16 Thread Alexander Burger
On Thu, May 16, 2013 at 04:33:49PM +0200, Alexander Burger wrote:
  though the less-recursive function is slightly slower (as expected).
 
 Oops, forget that! Not as expected ... the opposite would be expected.
 But the values are very close anyway.

One more oops!!

Though it doesn't matter much for the speed, I just noticed that 'ack2'
was wrong! It recursed on 'ack' instead of itself ('ack2').

So for the records, this is the corrected version:

   (de ack2 (M N)
  (loop
 (T (=0 M) (inc N))
 (NIL (=0 N) (ack2 (dec M) (ack2 M (dec N
 (dec 'M)
 (one N) ) )

♪♫ Alex
-- 
UNSUBSCRIBE: mailto:picolisp@software-lab.de?subject=Unsubscribe


Bouncing on trampolines (so cool...)

2013-05-15 Thread Samuel Dennis Borlongan
I'm currently playing a modified version of Hlavaty's trampoline function
which accepts functions with multiple parameters (
http://code.google.com/r/srborlongan-picolisp/source/browse/java/trampoline.l)
:

(de trampoline (F . @)
  (let A (rest)
(use R
  (loop
(NIL
  (car (setq R (apply F A) ) )
  (cdr R) )
(setq
  F (car R)
  A (cdr R) ) ) ) ) )

So far, I've successfully created test functions for mutual recursion,
factorial, fibonacci, and ackermann. Comments that precede function
definition are to be regarded as inspiration for said function.

(de even/t (N)
  (if (=0 N)
(done T)
(continue odd/t (- N 1) ) ) )

(de odd/t (N)
  (if (=0 N)
(done)
(continue even/t (- N 1) ) ) )

(de fact/t (N R)
  (default
R 1)
  (if ( N 2)
(done R)
(continue fact/t (- N 1)
  (* R N) ) ) )

#
http://www.solutionsiq.com/resources/agileiq-blog/bid/72880/Programming-with-Groovy-Trampoline-and-Memoize

(de fib/t (N R S)
  (default
R 1
S 1)
  (if ( N 2)
(done R)
(continue fib/t (- N 1)
  S (+ R S) ) ) )

#
http://stackoverflow.com/questions/12186672/how-can-i-prevent-my-ackerman-function-from-overflowing-the-stack

(de ack/t (M N R S)
  (default R '())
  (if ( (length R) 0)
(unless S
  (setq M (pop 'R) ) )
(push 'R M) )
  (cond ((=0 (length R) ) (done N) )
((= M 0) (continue ack/t M (+ N 1) R) )
((= M 1) (continue ack/t M (+ N 2) R) )
((= M 2) (continue ack/t M (+ (* N 2) 3) R) )
((= N 0) (continue ack/t (- M 1) 1 R T) )
(T
  (push 'R (- M 1) )
  (continue ack/t M (- N 1) R T) ) ) )

Honestly, I hate my Ackermann implementation, not because its performance
is bad ( (trampoline ack/t 3 8000) takes 1.970 sec and 10.287 sec in 32-bit
and ersatz, respectively), but because I have had a nagging suspicion that
there must something better than using a stack rather than a plain atom for
storing intermediate values.

One problem for me is that the other solution that has been presented
involves making the continue function return functions instead of lists,
which would mean I would have to rewrite the trampoline function.

That's the thing: Hlavaty's trampoline function is too gosh darn _elegant_
for me to rewrite. I mean, seriously, the way it manages to use basic list
manipulation to replace what would have been continuous function unwrapping
in other languages.

Thus, should I really complain that ack/t uses a stack if it means that the
underlying bouncing principles are still maintained.

Unless, of course, if using stacks in this context is stupid in the first
place.

P. S.

I apologize for the length and relative lack of quality of this post.
I'm actually feeling quite sleepy (GMT +8).
The sleepier I get, the more hyperactive I get.
Thus, long post.

P.P.S.

DaanSystem's LispIDE is cool.

Samuel Dennis R. Borlongan