Hi everybody,
Now that mandelbrot is pretty fast, I've thought about making it look
cleaner. I wrote the code several years ago so it doesn't use some of
the newer language features we have now.
This is the inner loop in benchmark.mandel:
: iter ( c z nb-iter -- x )
dup 0 <= [ 2nip ] [
over absq 4.0 >= [ 2nip ] [
>r sq dupd + r> 1- iter
] if
] if ; inline recursive
:: render ( accum -- )
height [
width swap [
c C{ 0.0 0.0 } nb-iter iter dup zero?
[ drop B{ 0 0 0 } ] [ color-map [ length mod ] keep nth ] if
accum push-all
] curry each
] each ; inline
'iter', which is called to compute pixel values, is pretty hairy. Note
that 'render' uses locals instead of 'make' to accumulate the final
sequence of pixel data; I thought this is a performance optimization
but it actually makes very little difference now that I time it.
What is iter doing? It counts down from 'nb-iter' (defined as :
nb-iter 40) until it either reaches zero, or the value 'z' on the
stack has a squared absolute value greater than or equal to 4 (absq
4.0 >=). But this pattern is already implemented by the
find-last-integer combinator in the math vocabulary.
Also, the input parameter 'c' is passed to each iteration, but it
never changes. This suggests that we can try currying it onto a
quotation to reduce stack shuffling.
Finally, now that 'iter' is no longer recursive, the setup code that
precedes it in 'render' can be moved into 'iter'.
Here is a new version using the above:
: iter ( c -- iterations )
[ C{ 0.0 0.0 } nb-iter ] dip
'[ drop sq , + dup absq 4.0 >= ] find-last-integer nip ;
inline
The new version no longer needs 2nip, over, or dupd. The only
remaining shuffle words operate on a single item at the top of the
stack. Also note the use of 'fry' to splice the value 'c' into the
middle of the quotation.
There is still a pattern here that is not expressed. We have a
function, f(z), which are are applying repeatedly to an initial value,
f(f(f(...f(z)..))), until the result either patches a predicate [ absq
4.0 >= ] or we reach the maximum number of iterations. Let's write a
new combinator which abstracts out the pattern:
: count-iterations ( z nb-iter step-quot test-quot -- #iters )
'[ drop @ dup @ ] find-last-integer nip ; inline
Finally, I renamed 'iter' to 'pixel', since the name is a bit more
descriptive. I also changed all of the code to use 'make' instead of
having a local 'accum' (% looks better than 'accum push-all'!).
Here is the final inner loop, after all of the above cleanups:
: count-iterations ( z nb-iter step-quot test-quot -- #iters )
'[ drop @ dup @ ] find-last-integer nip ; inline
: pixel ( c -- iterations )
[ C{ 0.0 0.0 } nb-iter ] dip
'[ sq , + ] [ absq 4.0 >= ] count-iterations ;
inline
: color ( iterations -- color )
[ color-map [ length mod ] keep nth ] [ B{ 0 0 0 } ] if* ;
inline
: render ( -- )
height [ width swap [ c pixel color % ] curry each ] each ;
inline
It's longer than the original but more readable. Also we're not using
locals anywhere -- the concatenative programming paradigm is perfectly
capable of expressing this algorithm without any outside help.
Slava
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Factor-talk mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/factor-talk