The arrays from the math library performs best when using operations that work on an entire axis (think row or column) at a time. Using array-ref and array-set! in a loop will be slower than using array-ref and array-set! from Typed Racket due to contract checking of the array.
But we can reduce the number of times the contract is checked.
A small benchmark reveals that the speed up from untyped Racket is well worth using array-getter or unsafe-array-getter.
array-ref cpu time: 4508 real time: 4531 gc time: 234
unsafe-array-ref cpu time: 3856 real time: 3874 gc time: 186
array-getter cpu time: 2091 real time: 2101 gc time: 85
unsafe-array-ref cpu time: 1355 real time: 1362 gc time: 55
The benchmark for the setter version:
array-set! cpu time: 5978 real time: 6007 gc time: 244
unsafe-array-set! cpu time: 5316 real time: 5341 gc time: 223
array-setter! cpu time: 2296 real time: 2305 gc time: 90
unsafe-array-setter cpu time: 1563 real time: 1570 gc time: 61
The whole benchmark:
#lang racket/base
(require math/array
"array-experiment.rkt")
(define M (mutable-array #[#[0 1] #[2 3]]))
(define M_ (array-getter M))
(define M! (array-setter M))
(define %M_ (unsafe-array-getter M))
(define %M! (unsafe-array-setter M))
(M_ #(0 0))
(M! #(0 0) 42)
(M_ #(0 0))
(define js (vector 0 0))
(time (for ([n (in-range 100000)])
(array-ref M js)))
(time (for ([n (in-range 100000)])
(unsafe-array-ref M js)))
(time (for ([n (in-range 100000)])
(M_ js)))
(time (for ([n (in-range 100000)])
(%M_ js)))
;; cpu time: 4508 real time: 4531 gc time: 234
;; cpu time: 3856 real time: 3874 gc time: 186
;; cpu time: 2091 real time: 2101 gc time: 85
;; cpu time: 1355 real time: 1362 gc time: 55
(time (for ([n (in-range 100000)])
(array-set! M js 42)))
(time (for ([n (in-range 100000)])
(unsafe-array-set! M js 42)))
(time (for ([n (in-range 100000)])
(M! js 42)))
(time (for ([n (in-range 100000)])
(%M! js 42)))
;; cpu time: 5978 real time: 6007 gc time: 244
;; cpu time: 5316 real time: 5341 gc time: 223
;; cpu time: 2296 real time: 2305 gc time: 90
;; cpu time: 1563 real time: 1570 gc time: 61
You're imagining a different kind of implementation for checking than what actually happens. Checking the array ends up with wrappers, and there's a wrapper on the result of your array-getter function as well. It's the wrappers that really cost here, and you can't get to a reasonable speed without eliminating them somehow.
Just a quick thought - is it gonna be just fixnums? Because if that is true, than I suggest developing the algorithm using Typed Racket and then just manually translate it to fxvectors using racket/unsafe/ops. Same applies for flonums. But definitely you have to handle multi-dimensionality yourself as those are one-dimensional by definition.
Or maybe unsafe-provide[1] is what you are looking for? It provides given provide spec without installing any contracts - so plain Racket code can use it without checking.
Have a form bless used as (bless value A). It creates an instance of a bless structure that contains the blessed value and some kind of "certificate" that identifies the type A.
It will have the type (Bless A).
When a contract is made for a function with the type (-> (Bless A) Any) it will use the "certificate" and if it matches A the contract checker just accepts the value without further checks.
This approach stops accidently passing a value of a wrong type to typed code. But it's still unsafe, since on the untyped side the value could be extracted and mutated.
Sorry for resurrecting this ancient thread. With the release of shallow typed racket, I'd be interested to know if it could help with performance when using the math lib from untyped racket.
Perhaps I'm misunderstanding what shallow typed racket really is, but couldn't it avoid the checking of deeply nested contracts? In that case, would providing a shallow typed racket wrapper for the math lib be enough? I think it's such a shame that such a beautiful math library is limited by those existing performance issues!