Plot library very slow with sqrt

Hello,
I'm working with the Plot library and I have a problem.
When I try to plot, for example, the square root, this
function i really fast:

(time (plot (function sqrt (- 0) 20)))
> cpu time: 359   real time: 358   gc time: 62

but this is really slow:

(time (plot (function sqrt (- 20) 20)))
> cpu time: 16750 real time: 16912 gc time: 406

This i really annoying. Is there a solution?

Matteo

It's because on [-20, 0), sqrt produces imaginary numbers (try it in the repl), and plot filters them out, which apparently is pretty costly. This happens with other functions such as log, or functions that produce +nan.0 on a large portion of the x-axis. I'm not too sure why though.

What's happening is that

  1. plot uses Typed Racket and specifies a type for the argument to function that it takes Real to Real.
  2. The type is translated to a contract when you use it in untyped Racket.
  3. When plot passes inputs like -10 to sqrt that fails the contract, triggering a contract error, which involves some expensive computation to print out the error message (something about packages).
  4. plot then catches the exception to just not show that point on the graph, and tries other values.
  5. Since this happens for a lot of negative values, the expensive error-formatting computation happens a lot of times, resulting in the slow program you see.

The most obvious optimization is for the contract error printing to not be so slow (why does it need to call path->pkg+subpath every time?) but maybe there's a way for plot to be smarter too.

4 Likes

Maybe a good idea is for plot change its type to Number -> Number and then to silently discard non-reals but to stop catching the exceptions (or just catch the first one and make sure that one gets displayed somehow in a useful way). Not that I'm a super heavy user of plot but it seems like I'd want to know if I passed a function in that was doing (car #f) or something like that, instead of returning a number. (Of course, this ship may have sailed for backwards compatibility reasons so maybe this has to be optional behavior.)

1 Like

The easiest workaround I’ve found is

(plot (function (lambda (x) (sqrt (abs x))) (- 20) 20))

A better solution is

(plot (function (lambda (x) (real-part (sqrt x))) (- 20) 20))

It’s also interesting to write

(plot (function (lambda (x) (imag-part (sqrt x))) (- 20) 20))

Matteo

1 Like

Just thinking aloud:
How about delaying the evaluation of the exception message until it is actually used?
Maybe using a parameter defaulting to immediate evaluation, and otherwise the exn message is a thunk that needs to be applied to obtain the actual string.

Is it faster to plot real-sqrt than sqrt?

(define (real-sqrt x)
  (if (and (real? x) (not (negative? x))
       (sqrt (* 1.0 x)
       #f))

A more interesting visualization is to use a parametric plot:

#lang racket
(require plot)

(parameterize ([line-samples 5000]
               [line-width 3])
  (plot
   (parametric
    (lambda (t)
      (let ([s (sqrt t)])
        (vector (real-part s) (imag-part s)))) -20 20 #:label "sqrt(t)")
   #:x-min -4 #:x-max 4 #:y-min -4 #:y-max 4
   #:x-label "Real Axis" #:y-label "Imaginary Axis"))

Produces:

sqrt-plot