[racket] fl vs. unsafe-fl

From: Matthew Flatt (mflatt at cs.utah.edu)
Date: Mon Sep 1 15:40:49 EDT 2014

Yes, using `fl` operations can often provide much of the benefit of
`unsafe-fl` operations. The JIT knows than a `fl` operation always
produces a flonum, so no check or boxing is needed if the flonum is
immediately consumed by another `fl` operation. (Boxing is a bigger
effect than dynamic checks, and no boxing is needed within the loop for
any of the examples below.)

An `unsafe-flvector` operation avoids array-bounds checks in addition
to fixnum and flonum checks on the second and third arguments. I think
that's why there's a much bigger difference between the second and
third examples below.

At Mon, 01 Sep 2014 19:12:44 +0400, Dmitry Pavlov wrote:
> Hello,
> 
> I was wondering whether operations with flonums and flvectors work
> slower than their unsafe- counterparts in intensive numerical tasks.
> 
> Here is an example: res = a+b*c
> 
> #lang racket
> 
> (require racket/flonum
>           racket/unsafe/ops)
> 
> (define n 1000)
> (define k 100000)
> 
> (define a (make-flvector n 1.0))
> (define b (make-flvector n 2.0))
> (define c (make-flvector n 3.0))
> (define res (make-flvector n))
> 
> (define (test-perf)
>    (for ((i (in-range k)))
>      (for ((j (in-range n)))
>        (flvector-set!
>         res j (fl+ (flvector-ref a j)
>                    (fl* (flvector-ref b j)
>                         (flvector-ref c j)))))))
> 
> (define (test-perf-unsafe-ops)
>    (for ((i (in-range k)))
>      (for ((j (in-range n)))
>        (flvector-set!
>         res j (unsafe-fl+ (flvector-ref a j)
>                           (unsafe-fl* (flvector-ref b j)
>                                       (flvector-ref c j)))))))
> 
> (define (test-perf-unsafe-flvec)
>    (for ((i (in-range k)))
>      (for ((j (in-range n)))
>        (unsafe-flvector-set!
>         res j (fl+ (unsafe-flvector-ref a j)
>                    (fl* (unsafe-flvector-ref b j)
>                         (unsafe-flvector-ref c j)))))))
> 
> (define (test-perf-unsafe-all)
>    (for ((i (in-range k)))
>      (for ((j (in-range n)))
>        (unsafe-flvector-set!
>         res j (unsafe-fl+ (unsafe-flvector-ref a j)
>                           (unsafe-fl* (unsafe-flvector-ref b j)
>                                       (unsafe-flvector-ref c j)))))))
> 
> (time (test-perf))
> (time (test-perf-unsafe-ops))
> (time (test-perf-unsafe-flvec))
> (time (test-perf-unsafe-all))
> 
> 
> Result (Racket 6.1.0.2, Linux 64-bit):
> 
> cpu time: 1704 real time: 1704 gc time: 0
> cpu time: 1692 real time: 1692 gc time: 0
> cpu time: 736 real time: 738 gc time: 0
> cpu time: 684 real time: 682 gc time: 0
> 
> 
> It turns out that replacing arithmetical operations
> on flonums with their unsafe- counterparts makes no difference,
> unless flvector access is unsafe- too; and even then, making
> flvector access unsafe makes a huge improvement, while acceleration
> from unsafe ops is about 7%.
> 
> I can imagine that the Racket's JIT skips checking the types of
> arguments of fl+ and fl* because it figures that since they come
> from flvectors, they must be flonums. Am I correct?
> 
> If I am correct, then are unsafe-fl arithmetical operations important
> at all? Because in numerical programs, nearly every flonum is either
> a constant, or a value taken from flvector, or a function argument
> that can be traced to the same.
> 
> 
> Regards,
> 
> Dmitry
> ____________________
>   Racket Users list:
>   http://lists.racket-lang.org/users

Posted on the users mailing list.