[racket-dev] Math library initial commit almost ready; comments on issues welcome

From: Neil Toronto (neil.toronto at gmail.com)
Date: Mon Oct 1 18:08:10 EDT 2012

On 10/01/2012 02:06 PM, Sam Tobin-Hochstadt wrote:
> On Mon, Oct 1, 2012 at 2:26 PM, Neil Toronto <neil.toronto at gmail.com> wrote:
> PR 13098 isn't really fixable, in some sense.  There's just more data
> there with broader types, so it will always take longer than for more
> specific types. All of the things I'd do to fix the problem would
> speed up both the slow and the fast case, without really affecting the
> gap between them.

You're saying it wouldn't hurt to replace functions with more 
specifically typed functions in general, right? Good point.

I think you can speed up the slow cases more than the fast ones, though. 
Huge `case->' types tend to have this structure:

   (case-> (A0 -> B0)
           ((U A0 A1) -> B1)
           ((U A0 A1 A2) -> B2)
           ((U A0 A1 A2 A3) -> B3)
           ...)

The `abs' function, for example, starts this way:

   (case-> (Zero -> Zero)
           (One -> One)
           (Positive-Byte -> Positive-Byte)
           (Byte -> Byte)
           (Positive-Index -> Positive-Index)
           (Index -> Index)
           ...)

I've done some timing tests using generated types like this:

   (case-> (Zero -> Zero)
           ((U Zero One) -> (U Zero One))
           ((U Zero One 2) -> (U Zero One 2))
           ...)

The tests suggest that typechecking application of a function with a 
type like that is quadratic in the size of the function's type, which 
makes sense. Could you get something nearer to linear-time by dividing 
the case types into monotone groups?

My timing tests also show that typechecking is apparently quadratic in 
the depth of expressions, regardless of how simple the types are. Is 
that something you already knew?

(The latter is a medium-ish deal for the math library. Most special 
functions have domains in which they're computed by evaluating a 25- to 
50-degree polynomial. Using Horner's method, that's an expression 50 to 
100 deep; if they're Chebyshev polynomials, it's more like 200-400.)

>>   * `math/base' re-exports `racket/math', but with extra constants (like
>> `phi.0') and functions (like `power-of-two?'). It also exports improved
>> hyperbolic functions, such as a new `sinh' that's much more accurate near
>> zero: in the worst case, 2e-16 relative error (which is great) instead of
>> 0.5 (which is really bad). But its type in `math/base' is
>>
>>    (case-> (Zero -> Zero)
>>            (Flonum -> Flonum)
>>            (Real -> Real)
>>            (Float-Complex -> Float-Complex)
>>            (Complex -> Complex))
>>
>> I haven't been able to give it a type as specific as the type of the `sinh'
>> exported from `racket/math'.
>
> Why aren't you able to do this?

Probably because I haven't tried hard enough. :p Also, every time I read 
its type, I get vertigo.

I know I've been unable to make a case type mirror the runtime branching 
done in a function before, even when the computation only happened in 
the leaves and I used nothing but predicates with filters. I don't 
remember the specifics, though; it was two months ago.

>>   * Another typed/untyped barrier issue: certain functions are actually
>> macros so that TR can optimize around their expansions. (Example: by making
>> `fcvector-ref' a macro, (+ (fcvector-ref xs 0) (fcvector-ref ys 0)) operates
>> entirely on unboxed Float-Complex values.) I really don't want to make an
>> `untyped/math' library to expose the function versions, but I can't think of
>> a way around it.
>
> There's no way around this at the moment.

I've managed to make some headway here in another part of the library, 
by defining macros only in #lang racket. If their expansions contain 
typed identifiers, it seems TR is smart enough to not check their 
contracts when the macros are used in typed code. Also, the optimizer 
can probably remove most of the sanity checks I'd need for things like 
`fcvector-ref'. Further, it looks like I can keep the macros in the 
files they're currently defined in using submodules.

This part could be less painful than I thought. That would be great.

Neil ⊥


Posted on the dev mailing list.