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

Reply via email to