On this page:
5.1 Turning the optimizer off
5.2 Getting the most out of the optimizer
5.2.1 Numeric types
5.2.2 Lists
5.2.3 Vectors
5.2.4 Performance Debugging
Version: 5.1.2

5 Optimization in Typed Racket

Typed Racket provides a type-driven optimizer that rewrites well-typed programs to potentially make them faster. It should in no way make your programs slower or unsafe.

For general information on Racket performance and benchmarking, see Performance.

5.1 Turning the optimizer off

Typed Racket’s optimizer is turned on by default. If you want to deactivate it (for debugging, for instance), you must add the #:no-optimize keyword when specifying the language of your program:

#lang typed/racket #:no-optimize

5.2 Getting the most out of the optimizer

Typed Racket’s optimizer can improve the performance of various common Racket idioms. However, it does a better job on some idioms than on others. By writing your programs using the right idioms, you can help the optimizer help you.

5.2.1 Numeric types

Being type-driven, the optimizer makes most of its decisions based on the types you assigned to your data. As such, you can improve the optimizer’s usefulness by writing informative types.

For example, the following programs both typecheck:
(define: (f (x : Real))  : Real  (+ x 2.5))
(f 3.5)
(define: (f (x : Float)) : Float (+ x 2.5))
(f 3.5)

However, the second one uses more informative types: the Float type includes only 64-bit floating-point numbers whereas the Real type includes both exact and inexact real numbers and the Inexact-Real type includes both 32- and 64-bit floating-point numbers. Typed Racket’s optimizer can optimize the latter program to use float -specific operations whereas it cannot do anything with the former program.

Thus, to get the most of Typed Racket’s optimizer, you should use the Float type when possible. For similar reasons, you should use floating-point literals instead of exact literals when doing floating-point computations.

When mixing floating-point numbers and exact reals in arithmetic operations, the result is not necessarily a Float. For instance, the result of (* 2.0 0) is 0 which is not a Float. This can result in missed optimizations. To prevent this, when mixing floating-point numbers and exact reals, coerce exact reals to floating-point numbers using exact->inexact. This is not necessary when using + or -. When mixing floating-point numbers of different precisions, results use the highest precision possible.

On a similar note, the Float-Complex type is preferable to the Complex type for the same reason. Typed Racket can keep float complex numbers unboxed; as such, programs using complex numbers can have better performance than equivalent programs that represent complex numbers as two real numbers. As with floating-point literals, float complex literals (such as 1.0+1.0i) should be preferred over exact complex literals (such as 1+1i). Note that both parts of a literal must be present and inexact for the literal to be of type Float-Complex; 0.0+1.0i is of type Float-Complex but 0+1.0i is not. To get the most of Typed Racket’s optimizer, you should also favor rectangular coordinates over polar coordinates.

5.2.2 Lists

Typed Racket handles potentially empty lists and lists that are known to be non-empty differently: when taking the car or the cdr of a list Typed Racket knows is non-empty, it can skip the check for the empty list that is usually done when calling car and cdr.

(define: (sum (l : (Listof Integer))) : Integer
  (if (null? l)
      0
      (+ (car l) (sum (cdr l)))))

In this example, Typed Racket knows that if we reach the else branch, l is not empty. The checks associated with car and cdr would be redundant and are eliminated.

In addition to explicitly checking for the empty list using null?, you can inform Typed Racket that a list is non-empty by using the known-length list type constructor; if your data is stored in lists of fixed length, you can use the List type constructors.

For instance, the type of a list of two Integers can be written either as:

(define-type List-2-Ints (Listof Integer))

or as the more precise:

(define-type List-2-Ints (List Integer Integer))

Using the second definition, all car and cdr-related checks can be eliminated in this function:
(define: (sum2 (l : List-2-Ints) : Integer)
  (+ (car l) (car (cdr l))))

5.2.3 Vectors

In addition to known-length lists, Typed Racket supports known-length vectors through the Vector type constructor. Known-length vector access using constant indices can be optimized in a similar fashion as car and cdr.

; #(name r g b)
(define-type Color (Vector String Integer Integer Integer))
(define: x : Color (vector "red" 255 0 0))
(vector-ref x 0) ; good
(define color-name 0)
(vector-ref x color-name) ; good
(vector-ref x (* 0 10)) ; bad

In many such cases, however, structs are preferable to vectors. Typed Racket can optimize struct access in all cases.

5.2.4 Performance Debugging

Typed Racket provides performance debugging support to help you get the most of its optimizer.

Setting the racket logging facilities to the 'warning level when compiling a Typed Racket program causes the optimizer to log each optimization it performs. Setting the Racket logging level can be done on the command line with the -W flag:

  racket -W warning my-typed-program.rkt

For example, the addition in the following program can be optimized:
#lang typed/racket
(: add-two-floats : Float Float -> Float)
(define (add-two-floats x y) (+ x y))

With optimizer logging turned on, the optimization is reported:
  TR opt: #f 5:0 + -- binary float

In addition, the optimizer also reports cases where an optimization was close to happening, but was not ultimately safe to perform. Such missed optimization warnings usually provide explanations as to why an optimization could not be performed, pointing to changes to your program that would make it more amenable to optimization.

For example, the multiplication below cannot be safely optimized (see above discussion about mixing reals and floats), although it may look like it can.
#lang typed/racket
(: mul-int-float : Integer Float -> Real)
(define (mul-int-float x y) (* x y))

With optimizer logging turned on, the missed optimization is reported:
  TR missed opt: #f 7:0 (#%app * x y) -- binary, args all float-arg-expr, return type not Float -- caused by: 7:0 x