为什么在无类型的模式中使用类型/球拍模块中的类会产生糟糕的性能?

时间:2017-11-27 09:12:03

标签: matrix scheme racket compiler-optimization typed-racket

  

有关更新,请参阅编辑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秒内完成。< / p>

缺点是我的无类型代码被类型感染,迫使我在TR中编写所有程序,让我很快就疯了。

但我的救世主并不是那么遥远。我偶然发现了一个有趣的四月愚蠢的包裹杰伊麦卡锡在阴天黑暗的夜晚写的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的矩阵运算可用。

谷歌小组关于杰伊写的疯狂模块的讨论是我能得到的唯一信息。

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它会在280毫秒内运行:

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

编辑2 - 这不是问题!

按照约翰克莱门特的回答,我发现这些例子不足以重现真正的问题。 在无类型的模块中使用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 ~a\n" 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从typed传递到untyped比传递class

更高效

使用结构而不是类修复了这个:

你好-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)))

用法:

#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

1 个答案:

答案 0 :(得分:2)

我正在写这个作为“答案”,允许我格式化我的代码......我想我们谈的有点过了。具体来说,我可以在大约半秒内从非类型化模块运行您键入的代码。我按照你的建议命名你输入的代码文件“hello-matrix.rkt”,然后运行你提供的无类型模块 (需要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

这是结果:

step1 <- c(0.0013807009, 0.0005997510, 0.0011314072, 0.0016246001, 0.0014240778)
A <- c( 34.648458,  1.705335,  0.000010, 11.312707,  9.167534)
n <- 10

tau <- matrix(0,nrow=n+1,ncol=5)
tau[1,] <- A
for(j in 1:5){
  for(i in 2:nrow(tau)){
    tau[i,j] <- tau[i-1,j] + step1[j]*1.0025^(i-2)
  }
}

也就是说,它需要花费相同的时间从无类型的球拍中调用它从键入的球拍中进行调用。

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

所有这一切都是为了证明TR代码仍然是TR代码,即使你从无类型代码中调用它。我确实认为你遇到了性能问题,我确实认为它们与矩阵运算有关,但这个特殊的例子没有说明它们。