[racket] fl vs. unsafe-fl
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