Matrix 为什么在非类型化模块中使用类型化/racket模块中的类会产生糟糕的性能?

Matrix 为什么在非类型化模块中使用类型化/racket模块中的类会产生糟糕的性能?,matrix,scheme,racket,compiler-optimization,typed-racket,Matrix,Scheme,Racket,Compiler Optimization,Typed Racket,有关更新,请参见编辑1、2和3。我把整个研究过程留在这里 我知道我们可以使用非类型racket中的类型化/racket模块(反之亦然)。但在执行此操作时,类型化/racket模块的行为就好像它是类型化/racket/no check,这将禁用优化并将其用作正常的非类型化模块 例如,如果您有这样一个键入的/racket模块: #lang typed/racket (require math) (provide hello) (define (hello [str : String]) (def

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

我知道我们可以使用非类型racket中的
类型化/racket
模块(反之亦然)。但在执行此操作时,
类型化/racket
模块的行为就好像它是
类型化/racket/no check
,这将禁用优化并将其用作正常的非类型化模块

例如,如果您有这样一个
键入的/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")
您希望在非类型化程序中使用它,如:

#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)在一个阴暗的夜晚写的一个有趣的类似四月愚人节的软件包,名为<代码>自由生存或死亡,它几乎做到了:

在我的
打字/拍子
模块中使用它,如下所示:

#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秒钟内运行,就像它是一个
键入的/racket
模块一样


当然,我对这种黑客行为很反感,这就是为什么我想知道是否有更好的解决方案,特别是对于使
数学中的矩阵运算可用

关于Jay写的疯狂模块的Google小组讨论是我能得到的唯一信息

这个帖子里的人似乎说这个模块不再有用了:

Matthias Felleisen
好吧,既然我们的年轻人已经很容易地揭穿了这个包裹,我们可以让它死,因为它不想再活了

真的有更好的选择吗


编辑1-一个可测试的示例 如果要测试性能差异,请尝试使用
的此定义执行一些疯狂的矩阵运算

#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 racket
自由活动或死亡
它以280ms的速度运行:

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

编辑2-这不是问题所在! 根据约翰·克莱门特的回答,我发现这些例子不足以再现真实的问题在非类型的模块中使用
类型化/racket
模块实际上效果很好。

我真正的问题是由一个类创建的边界契约的问题,该类从非类型传递到类型化的racket

让我们考虑<代码> hello矩阵.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)))
然后这两种用法:

#lang typed/racket
(require "hello-matrix.rkt")
(define crazy : Crazy (new crazy%))
(time (hello "Alan Turing" crazy))
cpu时间:1160实时:1178 gc时间:68

#lang racket
(require "hello-matrix.rkt")
(define crazy (new crazy%))
(time (hello "Alan Turing" crazy))
cpu时间:7432实时:7433 gc时间:80

使用
合同概要文件

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
从类型化传递到非类型化比传递
class
使用结构而不是类解决了以下问题:

你好矩阵。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)))
#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))
用法:

#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)))
#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时间:1008实时:1008 gc时间:52

#lang racket
cpu时间:996实时:995 gc时间:52

我写这个作为一个“答案”,允许我格式化我的代码。。。我想我们谈得有点过火了。具体来说,我可以在大约半秒钟内从非类型化模块运行您的类型化代码。按照您的建议,我将键入的代码文件命名为“hello matrix.rkt”,然后运行您提供的非类型化模块
#lang racket
(需要TR模块的那一个)并花费了相同的时间(大约半秒)。让我谨慎地说:

“hello matrix.rkt”的内容:

然后,我从一个非类型化模块调用它,就像你说的:

#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代码,即使您从非类型化代码调用它。我确实认为您存在性能问题,而且我确实认为这些问题与矩阵运算有关,但这个特定的示例没有说明这些问题。

我相信您误解了TR和racket之间的关系。具体而言,TR代码仍然是键入的;当在类型化世界中使用来自非类型化世界的值(必须用契约检查包装)时,通常会出现问题。这些合同中有许多是多余的,可以取消,但增加合同取消者的聪明度是一个正在进行的项目。为了更好地理解问题,你能提供一个完整的例子,简化但仍然显示问题吗?有许多技巧可能适用于你的案例。我用一个可测试的例子编辑了我的问题:)我大吃一惊。我想我的表现问题来自合同边界。我会检查并相应地更新我的问题!我也松了一口气,类型化和非类型化的球拍模块可以生活在一起!感谢您为我指出了正确的方向。请参阅我问题中的编辑2:)