为什么要在非类型的一个类型/球拍模块中使用类会产生不良的性能



有关更新,请参见编辑1、2和3。我在这里留下完整的研究过程。

我知道我们可以使用未型球拍(反之亦然)的typed/racket模块。但是,当这样做时,typed/racket模块的行为就像是typed/racket/no-check,它可以禁用优化并将其用作普通的未型模块。

例如,如果您有这样的typed/racket模块:

#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)))

,您想在这样的未经类似程序中使用它:

#lang racket/base
(require "hello-matrix.rkt")
(hello "Alan Turing")

您将获得相当糟糕的性能结果(在我的情况下,我正在执行大约600000个矩阵乘法,该程序甚至还没有完成),而使用#lang typed/racket则可以在3秒内完成我的程序完成。

不利的一面是,我的未构想的代码被类型感染,迫使我在TR中写下所有程序,使我很快发疯。

但是我的救主还不是很远。我偶然发现了一个有趣的四月般的包裹杰伊·麦卡锡(Jay McCarthy)在一个阴暗的夜晚,叫做live-free-or-die,这几乎可以做到这一点:

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!]))

通过在我的typed/racket模块中使用它,就像:

#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)))

现在,我的模块不再是#lang typed/racket,但是运行它的结果是壮观的!它在3秒内运行,就像是typed/racket模块一样。


我当然对那个黑客感到厌恶,这就是为什么我想知道是否可以更好地解决这个问题,尤其是用于从 math使用的矩阵操作。

Google组讨论了关于这个疯狂模块的讨论Jay写的是我唯一能获得的信息。

https://groups.google.com/forum/# !! topic/racket-users/jzohyxwwjqu

这个线程中的人似乎说模块不再有用:

MATTHIAS FELLEISEN
好吧,既然我们的年轻人很容易揭穿包裹,我们就可以让它死亡,因为它不再想要生活。

真的有更好的选择吗?


编辑1-可测试的示例

如果要测试性能差异,请尝试使用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"))

使用#lang typed/racket它在288ms中运行:

cpu time: 288 real time: 286 gc time: 16

使用#lang typed/racket/no-check它在52秒内运行:

cpu time: 52496 real time: 52479 gc time: 396

使用#lang racketlive-free-or-die它在280ms中运行:

cpu time: 280 real time: 279 gc time: 4

编辑2-这不是问题!

在约翰·克莱门特(John Clement)的回答之后,我发现这些例子还不足以复制真正的问题。使用typed/racket中的模块实际上可以正常工作。

我真正的问题是边界合同由从未型到打字球拍的类创建的问题。

让我们考虑一下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 ~an" str result)))

然后这两种用法:

#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

使用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

编辑3-将struct从输入到Untyped的传递比通过class

更具性能

使用结构而不是类解决以下内容:

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 ~an" str result)))

用法:

#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

我将其写为"答案",以便让我格式化我的代码...我认为我们互相谈论彼此。具体来说,我可以在大约半秒钟内从非类型的模块中运行您的键入代码。如您所建议(需要TR模块的一个),并且花费了相同的时间(大约半秒)。让我小心这样说:

" hello-matrix.rkt"的内容:

#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"))

然后,我从 untyped 模块中称其为:

#lang racket/base
(require "hello-matrix.rkt")
(time (hello "Alan Turing"))

这是结果:

Hello Alan Turing! Result is (array #[#[+inf.0 +inf.0 +inf.0 +inf.0 +inf.0] #[+inf.0 +inf.0 +inf.0 +inf.0 +inf.0] #[+inf.0 +inf.0 +inf.0 +inf.0 +inf.0] #[+inf.0 +inf.0 +inf.0 +inf.0 +inf.0] #[+inf.0 +inf.0 +inf.0 +inf.0 +inf.0]])
cpu time: 719 real time: 710 gc time: 231
Hello Alan Turing! Result is (array #[#[+inf.0 +inf.0 +inf.0 +inf.0 +inf.0] #[+inf.0 +inf.0 +inf.0 +inf.0 +inf.0] #[+inf.0 +inf.0 +inf.0 +inf.0 +inf.0] #[+inf.0 +inf.0 +inf.0 +inf.0 +inf.0] #[+inf.0 +inf.0 +inf.0 +inf.0 +inf.0]])
cpu time: 689 real time: 681 gc time: 184

也就是说,它需要同样的时间从未型球拍中调用它与打字球拍一样。

此结果可能取决于您正在使用的Drracket版本。我正在使用6.11。

所有这些都是为了证明TR代码仍然是TR代码,即使您从未经类似的代码调用它也是如此。我确实相信您遇到了性能问题,而且我确实相信它们与矩阵操作有关,但是这个特殊的例子没有说明它们。

最新更新