See EDIT 1, 2, and 3 for updates. I leave here the complete research process.
I know we can use typed/racket
modules from untyped racket (and vice versa). But when doing so, the typed/racket
module just behaves as if it was typed/racket/no-check
, which disables optimizations and just uses it as a normal untyped module.
For example, if you have a typed/racket
module like this:
#lang typed/racket
(require math)
(provide hello)
(define (hello [str : String])
(define result : (Matrix Flonum) (do-some-crazy-matrix-operations))
(display (format "Hello ~a! Result is ~a" str result)))
And you want to use it in an untyped program like this:
#lang racket/base
(require "hello-matrix.rkt")
(hello "Alan Turing")
You'll get pretty bad performance results (in my case, I'm doing about 600000 matrix multiplications, the program doesn't even finish), while using #lang typed/racket
makes my program finish in 3 seconds.
The downside is that my untyped code becomes infected with types, forcing me to write all my program in TR, turning me crazy pretty quickly.
But my savior was not so far away. I stumbled upon a funny April's-fool-like package Jay McCarthy wrote by a cloudy dark night, called live-free-or-die
, which pretty much does this:
http://docs.racket-lang.org/live-free-or-die/index.html
#lang racket/base
(require (for-syntax racket/base
typed-racket/utils/tc-utils))
(define-syntax (live-free-or-die! stx)
(syntax-case stx ()
[(_)
(syntax/loc stx
(begin-for-syntax
(set-box! typed-context? #t)))]))
(provide live-free-or-die!
(rename-out [live-free-or-die!
Doctor-Tobin-Hochstadt:Tear-down-this-wall!]))
By using it in my typed/racket
module, like so:
#lang racket
(require live-free-or-die)
(live-free-or-die!)
(require math)
(provide hello)
(define (hello str)
(define result (do-some-crazy-matrix-operations))
(display (format "Hello ~a! Result is ~a" str result)))
Now my module is not #lang typed/racket
anymore, but the results of running it are spectacular! It runs in 3 seconds, exactly as if it was a typed/racket
module.
I am, of course, disgusted with that hack, and that's why I'm wondering if there could be a better solution to this, especially for making the matrix operations from math
usable.
The Google Groups discussion about that crazy module Jay wrote is the only piece of information I could get.
https://groups.google.com/forum/#!topic/racket-users/JZoHYxwwJqU
People in this thread seems to say that the module is not useful anymore:
Matthias Felleisen
Well, now that our youngsters have easily debunked the package, we can let it die because it no longer wants to live.
Is there really a better alternative?
EDIT 1 - A testable example
If you want to test the performance difference, try using this definition of do-some-crazy-matrix-operations
:
#lang typed/racket
(require math)
(provide hello)
(: do-some-crazy-matrix-operations : (-> (Matrix Flonum)))
(define (do-some-crazy-matrix-operations)
(define m1 : (Matrix Flonum) (build-matrix 5 5 (lambda (x y) (add1 (random)))))
(define m2 : (Matrix Flonum) (build-matrix 5 5 (lambda (x y) (add1 (random)))))
(for ([i 60000])
(set! m1 (matrix-map * m1 m2))
(set! m2 (matrix-map * m1 m2)))
(matrix+ m1 m2))
(define (hello [str : String])
(define result : (Matrix Flonum) (do-some-crazy-matrix-operations))
(display (format "Hello ~a! Result is ~a" str result)))
(time (hello "Alan Turing"))
Using #lang typed/racket
it runs in 288ms:
cpu time: 288 real time: 286 gc time: 16
Using #lang typed/racket/no-check
it runs in 52 seconds:
cpu time: 52496 real time: 52479 gc time: 396
Using #lang racket
and live-free-or-die
it runs in 280ms:
cpu time: 280 real time: 279 gc time: 4
EDIT 2 - This was not the issue!
Following John Clement's answer, I discovered the examples were not enough to reproduce the real issue. Using typed/racket
modules in untyped ones actually works fine.
My real problem is an issue with the boundary contracts created by a class that passes from untyped to typed racket.
Let's consider this implementation of hello-matrix.rkt
:
#lang typed/racket
(require math)
(provide hello crazy% Crazy)
(define-type CrazyClass (Class (field [m1 (Matrix Flonum)])
(field [m2 (Matrix Flonum)])
(do (-> (Matrix Flonum)))))
(define-type Crazy (Instance CrazyClass))
(: crazy% CrazyClass)
(define crazy%
(class object%
(field [m1 (build-matrix 5 5 (lambda (x y) (add1 (random))))]
[m2 (build-matrix 5 5 (lambda (x y) (add1 (random))))])
(super-new)
(define/public (do)
(set! m1 (matrix* (matrix-transpose m1) m2))
(set! m2 (matrix* (matrix-transpose m1) m2))
(matrix+ m1 m2))))
(: do-some-crazy-matrix-operations : Crazy -> (Matrix Flonum))
(define (do-some-crazy-matrix-operations crazy)
(for ([i 60000])
(send crazy do))
(matrix+ (get-field m1 crazy) (get-field m2 crazy)))
(define (hello [str : String] [crazy : Crazy])
(define result : (Matrix Flonum) (do-some-crazy-matrix-operations crazy))
(display (format "Hello ~a! Result is ~a\n" str result)))
Then those two usages:
#lang typed/racket
(require "hello-matrix.rkt")
(define crazy : Crazy (new crazy%))
(time (hello "Alan Turing" crazy))
cpu time: 1160 real time: 1178 gc time: 68
#lang racket
(require "hello-matrix.rkt")
(define crazy (new crazy%))
(time (hello "Alan Turing" crazy))
cpu time: 7432 real time: 7433 gc time: 80
Using contract-profile
:
Running time is 83.47% contracts
6320/7572 ms
BY CONTRACT
g66 @ #(struct:srcloc hello-matrix.rkt 3 15 50 6)
3258 ms
(-> String (object/c (do (-> any/c (struct/c Array (vectorof Index) Index (box/c (or/c #f #t)) (-> Void) (-> (vectorof Index) Float)))) (field (m1 (struct/c Array (vectorof Index) Index (box/c (or/c #f #t)) (-> Void) (-> (vectorof Index) Float))) (m2 (struct/c Array (vectorof Index) Index (box/c (or/c #f #t)) (-> Void) (-> (vectorof Index) Float))))) any) @ #(struct:srcloc hello-matrix.rkt 3 9 44 5)
3062 ms
EDIT 3 - Passing struct
from typed to untyped is more performant than passing class
Using a struct instead of a class fixes this:
hello-matrix.rkt:
#lang typed/racket
(require math)
(provide hello (struct-out crazy))
(struct crazy ([m1 : (Matrix Flonum)] [m2 : (Matrix Flonum)]) #:mutable)
(define-type Crazy crazy)
(define (crazy-do [my-crazy : Crazy])
(set-crazy-m1! my-crazy (matrix* (matrix-transpose (crazy-m1 my-crazy))
(crazy-m2 my-crazy)))
(set-crazy-m2! my-crazy (matrix* (matrix-transpose (crazy-m1 my-crazy))
(crazy-m2 my-crazy)))
(matrix+ (crazy-m1 my-crazy) (crazy-m2 my-crazy)))
(: do-some-crazy-matrix-operations : Crazy -> (Matrix Flonum))
(define (do-some-crazy-matrix-operations my-crazy)
(for ([i 60000])
(crazy-do my-crazy))
(matrix+ (crazy-m1 my-crazy) (crazy-m2 my-crazy)))
(define (hello [str : String] [my-crazy : Crazy])
(define result : (Matrix Flonum) (do-some-crazy-matrix-operations my-crazy))
(display (format "Hello ~a! Result is ~a\n" str result)))
Usage:
#lang typed/racket
(require "hello-matrix.rkt")
(require math)
(define my-crazy (crazy (build-matrix 5 5 (lambda (x y) (add1 (random))))
(build-matrix 5 5 (lambda (x y) (add1 (random))))))
(time (hello "Alan Turing" my-crazy))
cpu time: 1008 real time: 1008 gc time: 52
#lang racket
cpu time: 996 real time: 995 gc time: 52