____________________
Racket Users list:
http://lists.racket-lang.org/users
In functions such as
(define (mix a b t)(+ (* (- 1 t) a) (* t b)))(define (fade t)(* t t t (+ (* t (- (* t 6) 15)) 10)))use floating point constants:(define (mix a b t)(+ (* (- 1. t) a) (* t b)))(define (fade t)(* t t t (+ (* t (- (* t 6.) 15.)) 10.)))/Jens Axel
____________________
Racket Users list:
http://lists.racket-lang.org/users
I just tried converting the code to Typed Racket using types loose
enough to ensure only functions needed to be annotated (i.e. mostly
(Vectorof Fixnum) and Real). I also changed it to use `images/flomap',
which is written in Typed Racket, instead of `picturing-programs', which
is untyped and doesn't have a typed interface. (That basically meant
changing `build-image' to `build-flomap*', and returning flvectors
instead of colors from the callbacks.) It took me about 10 minutes and
was straightforward. It would be a good first TR experiment.
Using "#lang typed/racket #:no-optimize" on both files, I get this:
> (flomap->bitmap (time (build-perlin-image 256 256)))
cpu time: 288 real time: 288 gc time: 68
- : (Instance Bitmap%)
<a bitmap>
Using "#lang typed/racket", I get this:
> (flomap->bitmap (time (build-perlin-image 256 256)))
cpu time: 284 real time: 283 gc time: 60
- : (Instance Bitmap%)
<a bitmap>
Two lessons:
1. The code will need to be specialized to flonums and fixnums for
TR's optimizer to do any real good.
2. Converting it to all typed code - critically, using a typed image
library - made it a lot faster anyway.
To explain #2: `images/flomap' was developed to do the low-level work
behind rendering Racket's icons, and takes full advantage of TR's and
the JIT's optimizations. Perhaps more importantly, because
"noisy-image-test.rkt" is statically checked, `build-flomap*' can
blindly accept its callbacks' return values.
Some tips:
* Annotation examples:
(: build-perlin-image (Integer Integer [#:scale Real] -> flomap))
(: grad3 (Vectorof (Vectorof Fixnum)))
(: perlin (case-> (Real -> Real)
(Real Real -> Real)
(Real Real Real -> Real)))
Be aware that `case->' types slow down type checking. (The function
body is checked once for each case.)
* Use (exact-floor x) instead of (inexact->exact (floor x)) so TR will
know that the result is an Integer. Use `fl' from `math/flonum' to
keep from having to type `real->double-flonum' everywhere. Don't use
`exact->inexact' in TR code. (See the `fl' docs for why not.)
* Using shadowing instead of `set!' allows Racket's JIT to generate
faster code, especially when the mutated value is a flonum.
I'm sure you can get this to generate 256x256 flomaps at interactive speeds.
Unfortunately, converting from a flomap to a bitmap takes a lot of time:
I'm measuring 90ms for a 256x256 image. I don't know why this is.
> To get the most of the TR optimizer, you can try the Optimization Coach
> DrRacket plugin. It reports the specializations that TR is doing and
> points out specialization opportunities that would require code/type
> changes to be safe.
Yes, do this. The only other option is examining fully expanded code in
the Macro Stepper and rediscovering the Optimization Coach's advice on
your own, which would be un-fun.
Neil ⊥
I just tried converting the code to Typed Racket using types loose enough to ensure only functions needed to be annotated (i.e. mostly (Vectorof Fixnum) and Real). I also changed it to use `images/flomap', which is written in Typed Racket, instead of `picturing-programs', which is untyped and doesn't have a typed interface. (That basically meant changing `build-image' to `build-flomap*', and returning flvectors instead of colors from the callbacks.) It took me about 10 minutes and was straightforward. It would be a good first TR experiment.
(: perlin (case-> (Real -> Real)
(Real Real -> Real)
(Real Real Real -> Real)))
Be aware that `case->' types slow down type checking. (The function
body is checked once for each case.)
* Use (exact-floor x) instead of (inexact->exact (floor x)) so TR will
know that the result is an Integer. Use `fl' from `math/flonum' to
keep from having to type `real->double-flonum' everywhere. Don't use
`exact->inexact' in TR code. (See the `fl' docs for why not.)
* Using shadowing instead of `set!' allows Racket's JIT to generate
faster code, especially when the mutated value is a flonum.
Yes, do this. The only other option is examining fully expanded code in the Macro Stepper and rediscovering the Optimization Coach's advice on your own, which would be un-fun.
No problem!
> Then I saw Float. At the moment, I'm not sure what the difference is
> between Flonum, Float, and Real, but using Flonum and Real have about
> the same runtime and Float is about twice as fast.*Why would that be the
> case?*
There shouldn't be any difference. AFAIK, Float is a synonym for Flonum.
The Real type is equivalent to (U Flonum Single-Flonum Exact-Rational).
A Real is anything you can apply `abs' to without raising an error.
> *Is there a difference in build-flomap* between returning a flomap or a
> flvector or (Vectorof Float)?* I can't seem to get the latter working...
The issue with (Vectorof Flonum) is that it's *not a subtype* of
(Vectorof Real), even though Flonum is a subtype of Real.
Say you returned a (vector 0.0 0.0 0.0) and *also* stored a reference to
it somewhere. Then `build-flomap*' named it `v' and did this to it:
(vector-set v 0 3/4)
which is legal because `build-flomap*' knows it's a (Vectorof Real). If
it was typed in your code as a (Vectorof Flonum), that would be unsound.
TR has no way to know that `build-flomap*' won't change the vector you
return to it, so it has to be very conservative. Mutable data structures
in TR are *invariant*.
You may have been expecting it to be *covariant* like an immutable data
structure. (Listof Flonum) *is* a subtype of (Listof Real).
> After the Float/Real/Flonum changes, I ended up writing this code:
>
> (: perlin (case-> (Real -> Real)
> (Real Real -> Real)
> (Real Real Real -> Real)))
> (define (perlin x [y 0.0] [z 0.0])
> (perlin^ (real->double-flonum x)
> (real->double-flonum y)
> (real->double-flonum z)))
>
> (: perlin^ (Float Float Float -> Float))
> (define (perlin^ x y z)
> ...)
>
> *Are there any obvious pitfalls I'm missing with this approach?*
Nope. It might not work out with more precise `case->' types, though,
e.g. if you wanted to return exact values given exact arguments.
> *Alternatively, when is this cost paid?* If it's at compile time, that's
> really not a big deal. If it's runtime, that's something I want to avoid.
Compile-time only.
> *As a side note, is it possible to type optional parameters inline?*
It's not possible, and it bothers me as well.
> .... I tried this:
>
> (: perlin (Real [Real] [Real] -> Real))
>
> That doesn't work.
Racket macros and languages don't generally distinguish paren shapes.
(It's hard to preserve that syntax property as macros expand.) IOW, this
works because `[...]' isn't special:
(: build-perlin-image (Integer Integer (#:scale Real) -> flomap))
> * Using shadowing instead of `set!' allows Racket's JIT to generate
> faster code, especially when the mutated value is a flonum.
>
>
> *Won't a series of defines already do this?* I replaced the 3 set!s in
> simplex with lets, but it didn't seem to actually change the runtime any.
I've noticed it making a difference in tight loops that accumulate a
flonum value.
> The Optimization Coach is really nice. I had to look up what the colors
> meant, but after that it's been most helpful.
>
> Although there is a lot more red when using Real instead of Flonum, but
> the runtimes seem about the same. Is it doing more optimizations than it
> appears or is there still more room to eek out some additional
> performance? Changing Real to Float makes everything nice and green.
I'm not sure. I'd expect green code to execute faster. Try running it in
DrRacket with debug info turned off (use the Language dialog) or from
the command line. If that doesn't change anything, there's probably one
or two expensive things left.
There shouldn't be any difference. AFAIK, Float is a synonym for Flonum.
Racket macros and languages don't generally distinguish paren shapes. (It's hard to preserve that syntax property as macros expand.) IOW, this works because `[...]' isn't special:
(: build-perlin-image (Integer Integer (#:scale Real) -> flomap))